擊左上方藍(lán)色“一口Linux”,選擇“設(shè)為星標(biāo)”
第一時(shí)間看干貨文章 ?【干貨】嵌入式驅(qū)動(dòng)工程師學(xué)習(xí)路線(xiàn) ?【干貨】Linux嵌入式知識(shí)點(diǎn)-思維導(dǎo)圖-免費(fèi)獲取 ?【就業(yè)】一個(gè)可以寫(xiě)到簡(jiǎn)歷的基于Linux物聯(lián)網(wǎng)綜合項(xiàng)目 ?【就業(yè)】簡(jiǎn)歷模版
那內(nèi)核加載到哪兒了?是加載到RAM里嗎?為什么不直接從Flash里運(yùn)行?MMU什么時(shí)候打開(kāi)的?設(shè)備樹(shù)是什么時(shí)候加載的?這時(shí)候很多人就開(kāi)始支吾了。
其實(shí)這塊知識(shí)點(diǎn)看起來(lái)簡(jiǎn)單,但問(wèn)下去會(huì)發(fā)現(xiàn)很多坑。今天我就把這些細(xì)節(jié)給大家掰開(kāi)了講講。
當(dāng)你按下電源鍵的時(shí)候,芯片內(nèi)部會(huì)做什么?
首先,芯片的電源管理單元會(huì)進(jìn)行初始化,時(shí)鐘開(kāi)始跑動(dòng)。然后芯片會(huì)執(zhí)行一個(gè)內(nèi)置的ROM代碼,這段代碼是芯片廠商在出廠時(shí)就燒進(jìn)去的,不可修改。一級(jí)Bootloader或者ROM Bootloader。
以RK3588為例,這是瑞芯微的一款八核高端SoC。當(dāng)RK3588上電時(shí),芯片內(nèi)置的ROM代碼會(huì)做這些事兒:
RK3588啟動(dòng)流程(第一級(jí)):
上電 → ROM代碼執(zhí)行
↓
檢測(cè)啟動(dòng)介質(zhì)(eMMC/SD卡/SPI Flash)
↓
從對(duì)應(yīng)位置讀取Bootloader(通常是U-Boot或Rockchip的miniloader)
位置通常是:
- eMMC: 第64個(gè)扇區(qū)開(kāi)始(0x20000字節(jié))
- SD卡: 第64個(gè)扇區(qū)開(kāi)始
- SPI Flash: 0x8000偏移處
↓
校驗(yàn)CRC32
↓
跳轉(zhuǎn)到RAM中執(zhí)行第二級(jí)Bootloader為什么?因?yàn)镽K3588的SRAM很小,只有幾百KB,裝不下整個(gè)U-Boot。所以Rockchip設(shè)計(jì)了一個(gè)小的miniloader先跑起來(lái),做最基礎(chǔ)的初始化,然后再加載完整的U-Boot。感興趣的可以看下這張圖.

U-Boot是嵌入式Linux領(lǐng)域最常用的Bootloader。一旦ROM Bootloader把U-Boot加載到RAM并跳轉(zhuǎn)過(guò)去,U-Boot就開(kāi)始工作了。
這時(shí)候的系統(tǒng)狀態(tài)是什么樣的呢?
CPU處于特權(quán)模式(比如ARM的SVC模式),MMU還沒(méi)打開(kāi),尋址方式還是物理地址。
以RK3588的U-Boot啟動(dòng)為例,整個(gè)過(guò)程大概是這樣的:
U-Boot啟動(dòng)時(shí)的主要工作流程:
1. 匯編階段(board_init_f)
├─ 關(guān)閉中斷和緩存
├─ 初始化CPU寄存器(棧指針、幀指針等)
├─ 檢測(cè)CPU類(lèi)型
├─ 初始化時(shí)鐘(RK3588需要配置PLL頻率)
├─ 初始化DRAM(對(duì)RK3588很關(guān)鍵,支持LPDDR4/LPDDR5)
└─ 清空BSS段
2. 重定位階段
├─ 計(jì)算U-Boot在RAM中的新位置
├─ 把U-Boot代碼從eMMC/SD卡復(fù)制到RAM的高地址
├─ (RK3588上通常是0x4FFE0000附近)
└─ 更新地址符號(hào)表
3. C語(yǔ)言階段(board_init_r)
├─ 初始化串口(看到U-Boot啟動(dòng)信息)
├─ 初始化eMMC/SD卡驅(qū)動(dòng)
├─ 初始化USB(用于USB燒寫(xiě)模式)
├─ 初始化網(wǎng)絡(luò)(支持網(wǎng)絡(luò)啟動(dòng))
└─ 加載內(nèi)核和設(shè)備樹(shù)為什么要初始化DRAM?以RK3588為例,它的DRAM最高可以是8GB。U-Boot啟動(dòng)時(shí)代碼還在eMMC里,讀取速度只有幾十MB/s。一旦DRAM初始化完成(這個(gè)過(guò)程通常要幾百毫秒),U-Boot就會(huì)把自己從eMMC搬到RAM的高地址區(qū)域,這樣運(yùn)行速度就能到GHz級(jí)了。
這個(gè)過(guò)程叫重定位。重定位之后,U-Boot可以快速加載內(nèi)核和設(shè)備樹(shù),也有足夠的空間來(lái)緩存數(shù)據(jù)。
在RK3588上,你啟動(dòng)時(shí)會(huì)看到這樣的信息:
DDR Version 1.08 20221121
LPDDR4X, 2112MHz
BW=32 Col=10 Bk=8 CS0 Row=16 CS=1 Die BW=16 Size=4096M
...
U-Boot 2022.11-rk (Jan 20 2026 - 12:00:00)
Model: Rockchip RK3588 Evaluation Board
DRAM: 4 GiB這說(shuō)明DRAM初始化成功了,識(shí)別到了4GB的LPDDR4X內(nèi)存,運(yùn)行頻率是2112MHz。
在board_init_r 開(kāi)始階段:

以下列表描述了上述提及的每種程序 圖示。
問(wèn)題:設(shè)備樹(shù)文件(.dtb)是什么時(shí)候加載的?
答案是:在U-Boot中加載的。
設(shè)備樹(shù)不是內(nèi)核自帶的,它是由Bootloader準(zhǔn)備好的。內(nèi)核啟動(dòng)時(shí),會(huì)從Bootloader傳遞的參數(shù)中獲取設(shè)備樹(shù)的地址,然后進(jìn)行解析。
在RK3588上,U-Boot會(huì)從eMMC里加載這些文件:
eMMC布局(RK3588典型配置):
0x00000 ─────────────────────
Bootloader (miniloader)
0x20000 ─────────────────────
U-Boot鏡像
0x80000 ─────────────────────
環(huán)境變量(可選)
0x100000 ────────────────────
內(nèi)核鏡像(Image或zImage)
0x400000 ────────────────────
設(shè)備樹(shù) (rk3588-evb.dtb)
0x500000 ────────────────────
根文件系統(tǒng)或其他U-Boot啟動(dòng)時(shí)的命令大概是這樣的:
# U-Boot交互式命令(你可以手動(dòng)輸入這些)
load mmc 0 0x50000000 Image
load mmc 0 0x5f000000 rk3588-evb.dtb
bootm 0x50000000 - 0x5f000000或者在U-Boot環(huán)境變量中配置自動(dòng)啟動(dòng):
bootcmd=load mmc 0 0x50000000 Image; load mmc 0 0x5f000000 rk3588-evb.dtb; bootm 0x50000000 - 0x5f000000這涉及到一個(gè)叫啟動(dòng)參數(shù)傳遞協(xié)議的東西。在ARM平臺(tái)上,通常是這樣:
所以當(dāng)U-Boot即將跳轉(zhuǎn)到內(nèi)核入口時(shí),它要確保這些寄存器的值設(shè)置正確。這樣內(nèi)核啟動(dòng)后,第一件事就是讀取R2寄存器,獲得設(shè)備樹(shù)在RAM中的地址(比如0x5f000000),然后開(kāi)始解析。
setup_start_tag(bd);
setup_memory_tags(bd); // 設(shè)置內(nèi)存信息
setup_commandline_tag(bd, commandline); // 設(shè)置kernel參數(shù)
setup_initrd_tag(bd, initrd_start, initrd_end); // 如果有initrd
setup_end_tag(bd); // 結(jié)束標(biāo)記

設(shè)備樹(shù)地址就這樣通過(guò)約定好的方式傳遞給了內(nèi)核。這也解釋了為什么如果設(shè)備樹(shù)位置不對(duì),內(nèi)核啟動(dòng)會(huì)出問(wèn)題——它根本找不到設(shè)備樹(shù)在哪兒。
現(xiàn)在進(jìn)入一個(gè)很多人容易搞混的地方:MMU什么時(shí)候打開(kāi)的?
答案是:在內(nèi)核啟動(dòng)的最后階段。
更準(zhǔn)確地說(shuō),是在內(nèi)核進(jìn)入main函數(shù)之后。讓我解釋一下這個(gè)過(guò)程:
內(nèi)核入口(匯編)
↓
檢查ATAGS或設(shè)備樹(shù)
↓
初始化頁(yè)表(在RAM中建立虛擬地址到物理地址的映射關(guān)系)
↓
打開(kāi)MMU和緩存
↓
虛擬地址空間生效,代碼運(yùn)行在虛擬地址上
↓
進(jìn)入C代碼(內(nèi)核main函數(shù))
↓
初始化各種子系統(tǒng)有個(gè)很重要的細(xì)節(jié):在打開(kāi)MMU之前和之后,PC(程序計(jì)數(shù)器)必須指向相同的代碼。
假設(shè)內(nèi)核代碼被加載到物理地址0x10000000,但在打開(kāi)MMU后,內(nèi)核要運(yùn)行在虛擬地址0xC0000000。如果你直接打開(kāi)MMU,PC會(huì)瞬間變成一個(gè)虛擬地址,而你沒(méi)有設(shè)置好這個(gè)虛擬地址的映射,CPU就懵了,會(huì)出錯(cuò)。
所以?xún)?nèi)核在打開(kāi)MMU時(shí),一定要設(shè)置一個(gè)identity mapping(恒等映射),也就是說(shuō),物理地址0x10000000和虛擬地址0x10000000要映射到同一塊內(nèi)存。這樣打開(kāi)MMU時(shí),代碼還是能繼續(xù)執(zhí)行。等MMU真正生效后,再切換到真正的虛擬地址空間。
這就是為什么有些內(nèi)核啟動(dòng)代碼看起來(lái)這么復(fù)雜——它得小心翼翼地在物理地址和虛擬地址之間切換,確保沒(méi)有任何跳躍。
有時(shí)候我們看到啟動(dòng)信息里有"Initrd"或"Initramfs"的字樣,這又是什么?
這是個(gè)很聰明的設(shè)計(jì)。Linux內(nèi)核啟動(dòng)后,需要加載驅(qū)動(dòng)、掛載根文件系統(tǒng),但問(wèn)題是——驅(qū)動(dòng)和文件系統(tǒng)本身還在磁盤(pán)上呢。要訪(fǎng)問(wèn)磁盤(pán)就需要磁盤(pán)驅(qū)動(dòng),要訪(fǎng)問(wèn)文件系統(tǒng)就需要文件系統(tǒng)驅(qū)動(dòng)。這就形成了一個(gè)循環(huán)依賴(lài)。
解決辦法是:Bootloader把一個(gè)壓縮的文件系統(tǒng)鏡像加載到內(nèi)存里,內(nèi)核啟動(dòng)時(shí)先使用這個(gè)內(nèi)存文件系統(tǒng)。等內(nèi)核啟動(dòng)到一定階段,有了足夠的驅(qū)動(dòng)和功能,再切換到真正的根文件系統(tǒng)。
Bootloader加載的內(nèi)容:
1. 內(nèi)核鏡像 → 0x10000000
2. 設(shè)備樹(shù) → 0x18000000
3. Initramfs → 0x18100000
內(nèi)核啟動(dòng):
1. 解壓內(nèi)核鏡像
2. 打開(kāi)MMU
3. 在Initramfs基礎(chǔ)上啟動(dòng),加載必要驅(qū)動(dòng)
4. 掛載真正的根文件系統(tǒng)
5. 執(zhí)行init進(jìn)程這就是為什么很多嵌入式系統(tǒng)啟動(dòng)時(shí)有一段明顯的延遲——這段時(shí)間就是內(nèi)核在加載initramfs,初始化各種驅(qū)動(dòng)。
我這些年遇到過(guò)很多啟動(dòng)問(wèn)題,總結(jié)一下最常見(jiàn)的幾個(gè):
問(wèn)題1:U-Boot卡住不動(dòng)
這通常是DRAM初始化失敗。以RK3588為例,如果DRAM初始化失敗,你會(huì)看到:
DDR Version 1.08 20221121
LPDDR4X, 2112MHz
BW=32 Col=10 Bk=8 CS0 Row=16 CS=1 Die BW=16 Size=4096M
... 然后就卡住了,后面沒(méi)有U-Boot的提示信息這說(shuō)明DRAM初始化在某個(gè)環(huán)節(jié)出問(wèn)題了。可能的原因:LPDDR5芯片接觸不好(RK3588上LPDDR5很敏感),時(shí)鐘配置不對(duì)(通常在設(shè)備樹(shù)中配置),電源供電不穩(wěn)定(特別是在高速率下)
排查方法:看U-Boot打印了多少信息。如果連"DRAM init"之前的信息都沒(méi)有,那就是最早的初始化出了問(wèn)題。可以在U-Boot的代碼里加printk調(diào)試,或者用JTAG單步執(zhí)行看看卡在哪兒。
問(wèn)題2:內(nèi)核啟動(dòng)起來(lái)了,但沒(méi)有輸出
這種情況通常是串口沒(méi)初始化。RK3588有多個(gè)UART,內(nèi)核需要知道使用哪個(gè)UART用于調(diào)試。這個(gè)信息存儲(chǔ)在設(shè)備樹(shù)或內(nèi)核命令行參數(shù)中。
在RK3588的設(shè)備樹(shù)中,例子這樣配置的:


排查方法:確認(rèn)chosen節(jié)點(diǎn)中的stdout-path指向了正確的UART,并且這個(gè)UART節(jié)點(diǎn)的寄存器地址與原理圖一致。
問(wèn)題3:Kernel panic
[ 0.123456] Unable to handle kernel NULL pointer dereference
[ 0.123457] PC is at ...
[ 0.123458] Call trace:內(nèi)核啟動(dòng)過(guò)程中突然崩潰。原因可能是MMU打開(kāi)時(shí)映射錯(cuò)誤,也可能是initramfs格式錯(cuò)誤,或者是某個(gè)驅(qū)動(dòng)初始化失敗。
對(duì)于RK3588,常見(jiàn)的panic原因包括:
排查方法:看Panic的調(diào)用棧,根據(jù)函數(shù)名定位是哪個(gè)驅(qū)動(dòng)出問(wèn)題了。如果是板級(jí)驅(qū)動(dòng),通常可以在內(nèi)核命令行加quiet來(lái)減少無(wú)關(guān)輸出,更清楚地看到panic。
總結(jié)一下從按下電源鍵到進(jìn)入Shell的完整過(guò)程,以RK3588為例:
T=0ms 按下電源鍵
↓
T=1-5ms RK3588內(nèi)置ROM代碼從eMMC讀取miniloader到SRAM
↓
T=5-20ms Miniloader初始化DDR(LPDDR4X或LPDDR5)
↓
T=20-50ms Miniloader加載完整的U-Boot到DDR
↓
T=50-100ms U-Boot啟動(dòng)
輸出:
"DDR Version 1.08 20221121
LPDDR4X, 2112MHz
...
U-Boot 2022.11-rk (Jan 20 2026 - 12:00:00)
Model: Rockchip RK3588 Evaluation Board
DRAM: 4 GiB"
↓
T=100-200ms U-Boot初始化eMMC,加載內(nèi)核和設(shè)備樹(shù)
"load mmc 0 0x50000000 Image"
"load mmc 0 0x5f000000 rk3588-orangepi-5-plus.dtb"
↓
T=200-210ms U-Boot跳轉(zhuǎn)到內(nèi)核入口地址0x50000000
↓
T=210-220ms 內(nèi)核匯編階段,初始化頁(yè)表,設(shè)置MMU和GIC
↓
T=220-300ms 內(nèi)核C代碼啟動(dòng),初始化時(shí)鐘、regulator、pinctrl
輸出:
"Linux version 5.10.0 #1 SMP ... (gcc version 10.0.0)"
"Booting Linux on physical CPU 0x0000000000 [0x410fd034]"
"Kernel command line: console=ttyFIQ0,1500000 root=/dev/mmcblk0p2"
↓
T=300-600ms 初始化各個(gè)子系統(tǒng)(GIC中斷控制器、時(shí)鐘、調(diào)節(jié)器)
初始化GPU(Mali-G710)、NPU、PCIE等
輸出大量的驅(qū)動(dòng)初始化信息
↓
T=600-1000ms加載initramfs,掛載根文件系統(tǒng)
執(zhí)行init進(jìn)程
↓
T>1000ms 系統(tǒng)進(jìn)入用戶(hù)空間,啟動(dòng)各個(gè)用戶(hù)進(jìn)程
"Started The Apache HTTP Server"
"Started Hostname Service"
...整個(gè)過(guò)程通常需要1-2秒鐘,具體時(shí)間取決于你的eMMC速度、編譯優(yōu)化和驅(qū)動(dòng)初始化的快慢。
啟動(dòng)過(guò)程看起來(lái)簡(jiǎn)單,但細(xì)節(jié)很多。大多數(shù)嵌入式工程師能把系統(tǒng)跑起來(lái),但很少有人真正理解中間發(fā)生了什么。這也是為什么很多啟動(dòng)問(wèn)題很難定位——你不理解系統(tǒng)狀態(tài),就看不出問(wèn)題在哪兒。
end
一口Linux
關(guān)注,回復(fù)【1024】海量Linux資料贈(zèng)送
精彩文章合集
文章推薦