1. 初始目标与环境
目标: 将RV1126开发板的启动方式,由从eMMC内部存储挂载根文件系统(rootfs),切换为通过网络挂载位于NFS服务器上的根文件系统。
动机: 提升开发调试效率,实现代码修改后仅需重启即可验证,免除eMMC的反复烧写。
硬件/软件环境:
目标板:RV1126
引导加载程序:U-Boot 2017.09
内核: Linux 4.19.111 (由Rockchip SDK的
./build.sh kernel
命令编译)启动镜像:
boot.itb
(eMMC默认),zboot.img
(编译产物)NFS/TFTP服务器IP:
192.168.1.78
目标板预设静态IP:
192.168.1.105
2. 调试历程与问题演进
问题一:bootargs
参数冲突,NFS设置被eMMC配置覆盖
操作: 初步尝试通过在U-Boot中设置
bootargs
和bootcmd
变量来引导NFS。现象: 重启后,开发板完全忽略了NFS设置,依然从eMMC启动了原有系统。
关键线索: 系统启动后,在串口终端执行
cat /proc/cmdline
,查看到内核实际接收到的启动参数[ 0.000000] Kernel command line: user_debug=31 storagemedia=emmc ... root=PARTUUID=614e0000-0000 rootfstype=ext4 ...
分析过程:
问题定位: 内核启动参数中完全没有我们设置的
nfsroot
等字段,反而存在明确指向eMMC分区的root=PARTUUID=...
。这证明U-Boot环境变量中的bootargs
在传递给内核的过程中被覆盖了。原理分析: 内核启动参数的来源主要有二:U-Boot环境变量和设备树(FDT)中的
/chosen
节点。Rockchip平台默认的boot_fit
命令会从eMMC加载一个FIT镜像,此镜像内部打包了内核和FDT。如果FDT的/chosen
节点内也定义了bootargs
属性,它将拥有极高的优先级。根源确认: 检查设备树源码
rv1126-alientek.dts
时,找到了问题的直接原因:
解决方案: 修改
rv1126-alientek.dts
文件,将/chosen
节点下的bootargs
属性整行删除。之后重新编译设备树(.dtb
),并用新的.dtb
重新打包FIT镜像,最后将这个“干净”的FIT镜像重新烧录到eMMC。此操作旨在从根源上消除冲突参数。
问题二:遭遇Kernel Panic
,深入内核排查驱动初始化时序
现象描述:在完成了2.1节中的设备树修改(删除/chosen
节点下的bootargs
属性)、重新编译并烧录了新的FIT镜像后,我重启了开发板。系统行为发生了关键变化:
系统不再从eMMC启动,证明2.1节的修改是有效的。
在U-Boot加载内核、打印
Starting kernel ...
之后,内核日志开始滚动,但在大约0.8秒后,系统完全崩溃,串口终端被Kernel Panic
信息刷屏。
关键线索(日志节选): 我从串口捕获了完整的崩溃日志。最核心的错误信息如下:
[ 0.194338] rk_gmac-dwmac ffc40000.ethernet: no regulator found
[ 0.826993] Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,255)
[ 0.837762] CPU: 2 PID: 1 Comm: swapper/0 Not tainted 4.19.111 #8
[ 0.838305] Hardware name: Generic DT based system
[ 0.838756] [<b010ff9c>] (unwind_backtrace) from [<b010c298>] (show_stack+0x10/0x14)
[ 0.839443] [<b010c298>] (show_stack) from [<b091afa4>] (dump_stack+0x90/0xa4)
[ 0.840083] [<b091afa4>] (dump_stack) from [<b0127318>] (panic+0xfc/0x26c)
[ 0.840692] [<b0127318>] (panic) from [<b0d01334>] (mount_block_root+0x218/0x2f4) ...
[ 0.945002] ---[ end Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,255) ]---
分析与思考过程
解读
Kernel Panic
:Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,255)
这句日志是Linux内核在启动流程中的“绝望呼喊”。VFS
(虚拟文件系统) 尝试挂载bootargs
中指定的根设备(在我们的NFS场景下,即nfs
设备),但最终失败了。error -6
(日志中未直接显示,但该错误码为此场景下的典型值) 代表-ENXIO
,即“无此设备或地址”。这证明,在内核需要使用网络来联系NFS服务器时,网络设备尚未就绪。定位核心矛盾: 此时,一个关键的矛盾点浮现出来:
事实A: 在从eMMC正常启动后,
ifconfig
可以看到eth0
,并且可以ping
通网络。这证明了物理硬件(SoC MAC + PHY芯片)是完好的。事实B: 我已经通过
make menuconfig
确认,内核的网络驱动CONFIG_DWMAC_ROCKCHIP
是以**内置(built-in,=y
)**方式编译的,并非模块(module,=m
)。这排除了因无法在早期加载.ko
驱动模块而导致NFS启动失败的常见问题。矛盾: 既然硬件完好、驱动也已内置于内核中,为何在NFS启动的这个特定时刻,网络设备却不可用?
提出“驱动时序”假设: 这种“最终能用,但早期不能用”的现象,是典型的**内核驱动初始化时序(Driver Initialization Timing)**问题。Linux内核在启动时,会按照一个复杂的依赖和优先级顺序来逐一初始化(probe)各个设备的驱动。NFS启动对网络就绪的时效性要求极为苛刻,它需要在内核启动的最初几百毫秒内就完成网络初始化。我的假设是:网卡驱动
dwmac
的初始化动作,早于了它所依赖的某个前置资源的初始化动作。寻找证据并验证假设:
no regulator found
这条日志,就是验证我假设的“铁证”。原理: 网卡驱动(
dwmac
)负责控制SoC内部的MAC逻辑,但它需要通过一个外部的PHY芯片来连接物理网线。这个PHY芯片是需要独立供电的。设备树作为硬件的软件描述,理应告诉dwmac
驱动,它需要使用的PHY电源(regulator)是哪一个。日志解读:
no regulator found
清晰地表明,dwmac
驱动在启动的第194毫秒,确实去向内核的电源管理框架(regulator framework)申请PHY芯片的电源了,但框架回复“找不到”。深层原因: 这并不是说电源不存在,而是在那个时刻,负责管理这个电源的PMIC驱动(在我们的板子上是
rk808-regulator
)自己都还没完成初始化,或者说,它还没来得及向内核注册它所管理的全部电源。因此,dwmac
的申请必定失败。
解决方案:在设备树中显式声明依赖关系
问题的根源在于
dwmac
驱动和regulator
驱动之间缺少一个明确的、可被内核驱动模型理解的依赖关系。解决方案必须从设备树入手,建立这个关系。软件映射 (设备树分析):
我打开了您提供的
rv1126-alientek.dtsi
文件,在i2c0
总线下的rk809: pmic@20
节点中,我找到了所有电源输出的定义。通过比对,硬件上的
SW4
(在数据手册中对应逻辑功能名DCDC_REG4
)在代码中的节点标签(handle)被清晰地定义为vcc3v3_sys
。
硬件溯源 (原理图分析):
我首先检查了您提供的PDF原理图。在第17页,确认了PHY芯片(
RTL8211F-CG
)的主3.3V供电网络标号为VCC33_LAN
。接着,在第13页的PMIC(
RK809
)电路部分,我找到了VCC33_LAN
的源头——它连接到U2A
芯片的SW4
引脚的输出网络VCC3V3_SYS
。硬件对应关系锁定:
PHY Power
->VCC33_LAN
->VCC3V3_SYS
->PMIC SW4
。
实施修改: 至此,硬件和软件的链条完全打通。然后修改rv1126-alientek.dtsi
文件,在&gmac
节点中,明确地添加下面这行至关重要的代码:它的作用是在编译层面为内核的驱动加载器设定了一条强制规则:“必须等待名为vcc3v3_sys
的regulator设备注册成功之后,才能对gmac
设备进行probe(初始化)”。这从根本上解决了驱动初始化的时序竞速问题。
在完成了此项修改后,重新编译并烧录了包含新设备树的FIT镜像后,然后从启动日志中清楚地看到,no regulator found
的警告消失了,取而代之的是:
这标志着驱动依赖与时序问题被成功攻克,接着进入下一个阶段.......
问题三:无法判断zboot.img镜像格式,使用booti
、bootz
、bootm
逐一排除
在完成了第二个问题中对设备树
phy-supply
的修复并重新烧录固件后,我满怀希望地认为问题已经解决。然而,重启后,Kernel Panic
依然如故。所有的静态配置(设备树)和依赖关系(电源)从理论上看都已无懈可击。因此,问题的焦点,决定性地从“配置”转向了U-Boot的“执行”——即bootcmd
的实际行为。此时我的核心判断是:Rockchip SDK默认的
boot_fit
命令行为过于复杂且不可控,它似乎总是以某种方式干扰bootargs
。为了获得对启动流程的100%控制权,最标准的工程做法就是彻底绕开它,转而使用最基础、最通用的TFTP加载方式,手动加载内核和设备树,再用标准命令启动。引入的新问题:未知的
zboot.img
镜像格式 这个思路立刻引入了一个新的关键变量:由SDK编译生成的内核镜像zboot.img
,究竟是什么格式?是纯粹的、无头部的
zImage
? -> 应该用bootz
命令。是带有U-Boot旧版头部的
uImage
? -> 应该用bootm
命令。是针对新架构的
Image
? -> 应该用booti
命令。
启动命令的选择,完全取决于镜像的格式。于是,我开始了一系列严谨的、逐一排除的实验。
实验A:使用
booti
命令的尝试原理与动机:
booti
是针对ARMv7/v8架构下、原始内核镜像(如zImage
/Image
)的较新启动命令。其语法booti <kernel_addr> - <fdt_addr>
非常清晰,是我首先想到的尝试。操作: 我在U-Boot中构造了如下命令:
setenv bootcmd_tftp '...; tftpboot ${kernel_addr_r} zboot.img; tftpboot ${fdt_addr_r} rv1126-alientek.dtb; booti ${kernel_addr_r} - ${fdt_addr_r}' setenv bootcmd 'run bootcmd_tftp' saveenv
现象与关键线索: 重启后,U-Boot在执行该命令时,立即打印出错误,启动中止。
Unknown command 'booti' - try 'help'
结论: 这个结果直截了当。它表明我当前使用的U-Boot版本在编译时,并未包含
booti
命令的支持。这证实了该U-Boot经过了功能裁剪,此路被完全堵死。
实验B:使用
bootz
命令的尝试与“意外的劫持”原理与动机:
booti
失败后,我转向了更经典的、同样用于启动zImage
的bootz
命令。同时,为了彻底搞清楚zboot.img
的身份,我计划在TFTP下载后,手动中断脚本,并使用iminfo
命令来一探究竟。操作: 我在串口终端手动执行了
tftpboot 0x0a200000 zboot.img
,准备下一步执行iminfo
。现象与关键线索: 出现了完全出乎意料的日志。U-Boot在下载完文件后,脚本并未停止等待我输入下一条命令,而是立刻开始尝试启动,并打印出截然不同的日志:
Loading: ####################################################### ... done Bytes transferred = 7593984 (73e000 hex) ## TFTP bootm zboot.img at 0x2008000 size 0x73e000 BOOTM: transferring to board FIT ## Booting FIT Image Sysmem Error: "KERNEL" ... alloc is overlap with existence "FIT_USER" ...
我本来想等他下载完之后,在uboot命令行执行iminfo去查看一下这个zboot.img到底是什么类型的内核镜像,可是我压根没机会去运行iminfo命令,他下载完成之后直接去starting kernel了,由此可以获得结论:
zboot.img
的身份被揭示:日志中的BOOTM: transferring to board FIT
是无可辩驳的铁证,证明了zboot.img
就是一个FIT镜像,而不是我之前假设的原始zImage
。U-Boot的“个性”被发现:更重要的是,这个U-Boot的
tftpboot
命令被赋予了“智能”,它在下载完可启动镜像后,会擅自、立即、自动地调用bootm
命令去启动它。这个“自动启动”特性,使得我们任何多步骤的脚本在tftpboot
这一步都会被“劫持”,后续指令根本没有机会执行。
实验C:使用
bootm
命令的尝试原理与动机: 既然知道了镜像是FIT格式,且
bootm
是对应的命令,我便尝试明确地使用bootm
,并为它提供一个独立的、我们修改过的设备树,期望能以此覆盖FIT镜像内部的FDT。操作: 构造了
bootm <kernel_addr> - <fdt_addr>
的命令。现象与关键线索: U-Boot再次报错并挂起:
FDT and ATAGS support not compiled in - hanging
结论: 这证实了该U-Boot被裁剪得非常彻底。它的
bootm
命令也被剥离了接收外部独立FDT的功能,它只能处理自包含的、单一的FIT镜像。
本阶段的解决方案与思路转变
分析总结: 经过这三个维度的逐一排除与失败,情况已经非常明朗。这个U-Boot是一个高度特化的“专才”,而不是一个功能全面的“通才”。它被编译的目的,就是为了以一种特定的方式(
boot_fit
)来引导一个特定的镜像格式(FIT)。任何试图使用通用的、将内核和设备树作为独立文件加载的方案(无论是用booti
,bootz
, 还是功能不全的bootm
),对于这个平台来说,都是行不通的。解决方案: 此时,唯一的、合乎逻辑的解决方案是:必须放弃所有绕开或替换
boot_fit
的尝试,回归到平台原生的boot_fit
启动路径上来。思路转变: 我们的调试重心,必须从“更换U-Boot启动命令”,重新转回到“在不改变
boot_fit
命令的前提下,如何精确地控制其运行环境”上来。这个思路的根本性转变,为我们解决后续的变量展开和时序问题,奠定了正确的方向。我们必须停止与系统“对抗”,而是学着去“利用”它的特性。下一步: 这直接引出了我们的下一个挑战——问题四:既然必须用
boot_fit
,那我们如何解决bootargs
变量在boot_fit
执行过程中无法正确展开的问题?
问题四:U-Boot变量未展开,内核收到“无法理解的”错误参数
现象描述: 在经历了命令与镜像格式的迷局后,我们被迫回归到唯一可行的
boot_fit
方案,并创造性地使用ping
命令来代替sleep
解决网络时序问题。然而,在满怀期待地重启后,系统依然Kernel Panic
。关键线索: 这一次,我们吸取了教训,第一时间检查内核收到的
Kernel command line
,终于发现了那个隐藏最深的错误:[ 0.000000] Kernel command line: user_debug=31 root=/dev/nfs nfsroot=${serverip}:${nfs_dir},v3,tcp ip=192.168.1.105...
ip=
部分是我们硬编码的,所以是正确的。但nfsroot=
部分,内核收到的竟然是${serverip}
和${nfs_dir}
这两个未经展开的变量名!分析过程:
问题定位: U-Boot环境变量展开失败。
原理分析:
ping ${serverip}
命令执行时,serverip
被正确展开了。但是,在我们构造的bootcmd_ultimate_nfs
脚本中,执行setenv bootargs ${bootargs_nfs}
时,U-Boot的脚本解释器只是简单地将bootargs_nfs
变量的字面内容(一个包含${...}
的字符串)赋给了bootargs
,而没有进行二次展开。根源确认: 内核不具备解析U-Boot变量语法的能力。当它看到
nfsroot=${serverip}:${nfs_dir}
时,视其为无效路径,从而导致NFS客户端无法启动,最终引发Kernel Panic
。
解决方案: 为了彻底规避U-Boot脚本解释器在嵌套和多级解析上的不确定性,我们必须采用最直接、最无歧义的方法:硬编码(Hardcoding)。即在
setenv bootargs
命令中,直接写入最终的、不包含任何${...}
变量的、纯净的字符串。最终的bootargs为:bootargs_nfs=root=/dev/nfs nfsroot=192.168.1.78:/home/alientek/workspace/rv1126/src_code/sdk_linux/buildroot/output/alientek_rv1126/target,v3,tcp ip=192.168.1.105:192.168.1.78:192.168.1.1:255.255.255.0::eth0:off rw console=ttyFIQ0,1500000
问题五:胜利的假象?——挂载成功但系统“瘫痪”,空的/proc
与/sys
现象描述: 在解决了U-Boot变量展开、FIT镜像配置覆盖、内核驱动时序等一系列底层问题后,系统终于不再
Kernel Panic
。内核启动日志滚动到最后,没有出现崩溃信息,串口输出停止。这看起来像是成功了。然而,系统却处于一种“瘫痪”状态:尝试通过
ssh root@192.168.1.105
登录,连接被拒绝(Connection refused)或超时(timeout),证明SSH服务没有启动。Telnet服务同样无法连接。
串口终端在内核启动日志结束后,没有任何反应,没有打印出
login:
提示符。在一个偶然的、能得到一个极简陋shell的启动版本中,执行
ls /proc
和ls /sys
,发现这两个目录是空的。进而导致ps
、top
、free
、mount
等所有依赖于这两个伪文件系统的核心命令全部失效。
分析与思考过程:
定位问题边界: 内核没有崩溃,证明了从U-Boot到内核加载,再到NFS挂载操作本身是成功的。问题已经从我们之前一直纠结的内核空间(Kernel Space),决定性地转移到了用户空间(User Space)。也就是说,内核已经把控制权交给了NFS服务器上的
/sbin/init
进程,但这个init
进程没能把系统完全地、正确地“拉起来”。剖析根文件系统源: 我回顾了NFS服务器的配置,共享的目录是
.../buildroot/output/alientek_rv1126/target
。这个目录是Buildroot在执行完所有软件包的编译和安装后,生成的一个**“暂存区”或“安装目录”。它包含了根文件系统的所有文件和目录结构蓝图,但它不是一个最终的、可启动的根文件系统**。阐述原理——“暂存区”与“成品”的区别: 一个能够让Linux系统正常运行的根文件系统,除了包含程序和库文件外,还必须满足几个关键条件,而这些恰恰是
target
目录所缺失的:正确的文件权限: 尤其是
/bin
,/sbin
,/etc
下的可执行文件和脚本,必须有正确的执行权限。Buildroot的target
目录在打包成最终镜像前,不保证所有权限都已正确设置。关键的设备节点:
/dev
目录下必须存在console
,null
,tty
,zero
等一系列基础设备节点。没有这些节点,init
进程甚至无法正确地将日志输出到控制台,SSH等服务也无法创建伪终端。Buildroot的target
目录下的/dev
通常是空的,这些节点是在打包成.ext4
或.tar
镜像的过程中,由mdev
或systemd
的机制动态或静态创建的。完整的启动脚本和服务配置:
/etc/init.d/
或systemd
服务必须被正确配置,能够执行挂载/proc
、/sys
、/dev/pts
等伪文件系统的任务,并启动如dropbear
(SSH服务)或telnetd
等关键网络服务。target
目录中的配置可能不完整或不适用。
最终假设: 内核成功挂载了
target
目录,但由于该目录本质上是一个“半成品”,它缺少正确的设备节点和文件权限,导致/sbin/init
进程在启动初期就因无法打开基础设备(如/dev/console
)而失败或挂起。因此,它未能执行后续的启动脚本来挂载/proc
和/sys
,也未能启动dropbear
(SSH服务),导致了我们观察到的系统“瘫痪”现象。
解决方案与验证: 解决方案是停止使用临时的
target
目录,转而使用Buildroot生成的最终的、完整的根文件系统镜像。创建正确的根文件系统挂载点: 我在NFS服务器上,找到了由Buildroot生成的最终产物
rootfs.ext4
镜像文件。# 在NFS服务器上执行以下操作 # 1. 创建一个新的目录作为我们真正的NFS根目录 mkdir /home/alientek/workspace/rv1126/src_code/sdk_linux/rv1126_nfs_rootfs # 2. 使用loop设备,将.ext4镜像挂载到这个新目录 # 这步操作相当于把一个硬盘分区“解压”到了一个文件夹里 sudo mount -o loop /path/to/your/buildroot/output/images/rootfs.ext4 /home/alientek/workspace/rv1126/src_code/sdk_linux/rv1126_nfs_rootfs
配置NFS服务器: 我修改了
/etc/exports
文件,将共享目录指向这个新的、正确的挂载点,并重启了NFS服务。# /etc/exports 文件中的修改 # 旧: /home/alientek/.../target *(...) # 新: /home/alientek/workspace/rv1126/src_code/sdk_linux/rv1126_nfs_rootfs *(rw,sync,no_root_squash,no_subtree_check)
修改U-Boot启动参数: 最后,我回到U-Boot命令行,修改了
bootcmd
中的硬编码路径,使其指向新的NFS共享目录。setenv bootargs 'root=/dev/nfs nfsroot=192.168.1.78:/home/alientek/workspace/rv1126/src_code/sdk_linux/rv1126_nfs_rootfs,v3,udp ip=192.168.1.105:192.168.1.78:192.168.1.1:255.255.255.0::eth0:off rw console=ttyFIQ0,1500000 storagemedia=emmc androidboot.storagemedia=emmc androidboot.mode=normal' saveenv
最终验证: 重启开发板后,内核成功挂载了新的、从
rootfs.ext4
镜像中来的完整根文件系统。init
进程正常执行,/proc
和/sys
被自动挂载,SSH服务(dropbear
)也成功启动。最终,我通过ssh root@192.168.1.105
成功登录,所有命令工作正常。问题彻底解决。
4. 总结与反思
此次调试历程,从一个看似简单的目标开始,最终演变为一场涉及U-Boot、设备树、内核和用户空间的全链路排查。它雄辩地证明了,任何一个环节的微小疏忽,都可能导致整个系统的启动失败。
eMMC与NFS启动的差异不仅仅在于
bootargs
,更在于对根文件系统完整性和正确性的要求。Buildroot的
target
目录是一个常见的陷阱,它只可用于检查文件内容,而不能作为可启动的根文件系统直接使用。一个健壮的调试流程,需要我们对从Bootloader到用户空间的整个启动链有清晰的认识,并能通过日志,将问题精确定位到其所属的层面。