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到用戶空間的整個啟動鏈有清晰的認識,并能通過日志,將問題精確定位到其所屬的層面。