1. 项目背景与问题定位
去年在给mini2440开发板移植官方Linux内核时,遇到一个典型的NFS挂载问题:内核启动后卡在"Waiting for root device /dev/nfs"阶段,无法正常挂载NFS根文件系统。这个问题困扰了我整整两天,最终通过分析内核启动流程和网络驱动加载时序才找到解决方案。现在把完整的排查过程和修复方案整理出来,给遇到类似问题的开发者参考。
mini2440作为经典的ARM9开发板,在嵌入式Linux学习中具有标本意义。官方内核移植通常会遇到两类典型问题:一是硬件适配(时钟、GPIO等初始化),二是外设驱动(特别是网络和存储)。我这次遇到的就是第二类问题中的网络文件系统挂载异常,具体表现为:
- 内核正常启动到挂载阶段
- 打印出"IP-Config: Complete"显示网络配置成功
- 但随后卡在"Waiting for root device /dev/nfs"无响应
- 手动触发中断后查看网络接口未成功初始化
2. 环境准备与基础配置
2.1 硬件环境确认
首先需要确认硬件基础配置是否正确:
- mini2440开发板(ARM920T核心)
- 配套的DM9000网卡芯片
- 通过USB转串口连接调试终端
- 主机端搭建的NFS服务器(我用的Ubuntu 18.04)
关键点:务必用万用表测量网卡的3.3V供电电压是否稳定,这个细节后来证明很重要
2.2 内核配置要点
在menuconfig中确保以下选项已启用:
code复制CONFIG_NETDEVICES=y
CONFIG_NET_ETHERNET=y
CONFIG_DM9000=y
CONFIG_ROOT_NFS=y
CONFIG_IP_PNP=y
CONFIG_IP_PNP_DHCP=y
特别注意DM9000的资源配置要与硬件一致:
c复制static struct resource dm9000_resources[] = {
[0] = {
.start = 0x20000000, // 根据原理图确认
.end = 0x20000000 + 3,
.flags = IORESOURCE_MEM
},
[1] = {
.start = 0x20000000 + 4,
.end = 0x20000000 + 7,
.flags = IORESOURCE_MEM
},
[2] = {
.start = IRQ_EINT7, // 中断号根据板子设计
.end = IRQ_EINT7,
.flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHEDGE
}
};
3. 问题排查全过程
3.1 第一阶段:基础检查
- 网络连通性测试:用ping命令确认主机与开发板物理连接正常
- NFS服务验证:在主机端通过
showmount -e确认共享目录可见 - 内核参数检查:确认bootargs正确设置:
code复制console=ttySAC0,115200 root=/dev/nfs nfsroot=192.168.1.100:/nfsroot ip=192.168.1.200:192.168.1.100:192.168.1.1:255.255.255.0::eth0:off
发现问题:即使这些配置都正确,仍然卡在挂载阶段。
3.2 第二阶段:内核日志分析
通过dmesg查看内核启动日志,发现关键线索:
code复制dm9000 Ethernet Driver, V1.31
eth0: dm9000 at c486e000,c486e004 IRQ 51 MAC: 00:09:c0:ff:ee:dd
IP-Config: Complete:
device=eth0, addr=192.168.1.200, mask=255.255.255.0
gw=192.168.1.1, host=192.168.1.200, domain=, nis-domain=
bootserver=192.168.1.100, rootserver=192.168.1.100, rootpath=
Waiting for root device /dev/nfs...
注意到虽然网络配置显示完成,但后续没有网卡初始化成功的打印信息。
3.3 第三阶段:驱动加载时序分析
通过在内核源码中添加打印,发现DM9000驱动probe函数执行时间过晚:
c复制// 在drivers/net/ethernet/davicom/dm9000.c中添加
printk(KERN_INFO "DM9000 probe called at %llu ns\n", ktime_get_ns());
对比正常系统,发现DM9000驱动应该在网络配置前完成初始化。而在我们的case中,顺序是:
- 内核启动网络协议栈
- 尝试配置IP(此时网卡还未ready)
- 最后才初始化DM9000驱动
4. 解决方案与验证
4.1 修改设备初始化顺序
通过调整内核初始化级别,确保网卡驱动先加载:
c复制// 在板级文件(mach-mini2440.c)中修改
static struct platform_device *mini2440_devices[] __initdata = {
&mini2440_device_eth, // 将网卡设备提到前面
&s3c_device_usb,
&s3c_device_lcd,
&s3c_device_wdt,
&s3c_device_i2c0,
&s3c_device_ts,
&s3c_device_rtc,
};
4.2 增加驱动超时检测
在dm9000_probe()中添加超时检测逻辑:
c复制// 修改drivers/net/ethernet/davicom/dm9000.c
static int dm9000_probe(struct platform_device *pdev)
{
// 增加电源稳定检测
int i;
for (i = 0; i < 100; i++) {
if (dm9000_read_locked(db, DM9000_VID_L) != 0x9000) {
msleep(10);
continue;
}
break;
}
if (i == 100) {
dev_err(&pdev->dev, "DM9000 not responding\n");
return -ENODEV;
}
// ...原有代码...
}
4.3 验证步骤
-
重新编译内核并烧写:
bash复制make uImage && sudo minicom -D /dev/ttyUSB0 -b 115200 -
观察启动日志,确认驱动加载顺序:
code复制DM9000 probe called at 123456789 ns eth0: dm9000 at c486e000,c486e004 IRQ 51 MAC: 00:09:c0:ff:ee:dd IP-Config: Complete... VFS: Mounted root (nfs filesystem) on device 0:11. -
测试NFS读写速度:
bash复制dd if=/dev/zero of=testfile bs=1M count=10
5. 经验总结与避坑指南
5.1 关键发现
- 电源稳定性:实测发现DM9000对3.3V电源纹波敏感,建议在VCC引脚加装0.1uF去耦电容
- 中断触发方式:硬件设计使用高电平触发,但内核默认配置可能不符,需检查:
c复制
.flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHEDGE - 时钟延迟:在
dm9000_probe()中增加50ms延迟可提高初始化成功率
5.2 调试技巧
-
通过
proc/interrupts查看中断触发次数:bash复制cat /proc/interrupts | grep eth0 -
使用
ethtool诊断链路状态:bash复制
ethtool eth0 ethtool -S eth0 -
内核启动时添加
netdev=5参数可输出详细网络调试信息
5.3 推荐工具链
- 交叉编译器:使用官方推荐的arm-linux-gcc 4.4.3
- 调试工具:
- J-Link EDU+J-Flash用于NOR Flash烧写
- OpenOCD+USB转串口用于调试
- 网络分析:
- Wireshark抓包分析
- tcpdump实时监控
6. 扩展应用场景
这套调试方法同样适用于:
- 其他ARM9开发板的NFS挂载问题(如S3C2410/S3C2440)
- 嵌入式系统中网络驱动的时序问题调试
- 根文件系统切换场景(如从NFS切换到YAFFS2)
- 低功耗模式下的网络唤醒问题
在实际项目中,我还发现通过调整内核的initcall级别可以优化驱动加载顺序。例如将关键驱动标记为arch_initcall:
c复制arch_initcall(dm9000_init);
这确保驱动在内核启动早期就被加载,避免与根文件系统挂载产生时序冲突。这个技巧在需要快速启动的工业控制场景特别有用。