嵌入式 Linux 啟動流程詳解 (以 ARM + U-Boot 為例)
對于嵌入式開發者而言,深入理解系統的啟動流程至關重要。這不僅有助于進行底層驅動開發和系統移植,還能在遇到啟動失敗等問題時,快速定位和解決。本文將詳細分解基于 ARM 架構的嵌入式 Linux 系統最常見的啟動流程:BootROM -> U-Boot -> Kernel -> RootFS
。過段時間仔細研究一下Uboot,看看能不能給手上這塊3506移植個Uboot試試。
啟動流程總覽
嵌入式設備的啟動過程是一個環環相扣的鏈條,前一級加載器負責初始化基礎硬件,并加載下一級更復雜的程序,直到最終啟動完整的 Linux 操作系統。
graph TDsubgraph SoC 內部芯片A[電源開啟] --> B(BootROM);endsubgraph 引導加載程序 (Bootloader)B --> |從 Flash/SD卡 加載| C[U-Boot SPL];C --> |初始化DDR| D[U-Boot 主體];endsubgraph 內核與用戶空間D --> |執行 bootcmd| E[加載 Linux 內核 (zImage)];D --> |執行 bootcmd| F[加載設備樹 (DTB)];E & F --> G{啟動內核};G --> H[內核初始化硬件];H --> I[掛載根文件系統 (RootFS)];I --> J[執行第一個用戶進程 /sbin/init];J --> K[啟動系統服務與應用];end
1. BootROM - 芯片內的第一行代碼
BootROM 是整個啟動鏈的起點。
- 是什么:一段固化在 SoC (System on Chip) 芯片內部的只讀存儲器 (ROM) 中的程序。它由芯片制造商編寫,用戶無法修改,是上電后 CPU 執行的第一段代碼。
- 核心職責:
- 最小化硬件初始化:進行最基礎的硬件設置,如配置內部時鐘、初始化用于加載下一階段代碼的存儲接口(如 SD 卡控制器、eMMC、SPI Flash 等)。
- 加載外部引導程序:根據預設的啟動介質順序(通常由一組
BOOT_MODE
引腳的電平狀態決定),從外部存儲設備(Flash, eMMC, SD Card)中查找并加載第二階段的引導加載程序(通常是 U-Boot 的一個小型版本SPL
)到芯片內部的 SRAM 中。 - 移交控制權:加載完成后,將 CPU 的執行權限交給這段剛剛加載進來的代碼。
BootROM 的工作非常單一,就是為更強大的 Bootloader 搭建一個最小化的運行環境。
2. U-Boot - 通用引導加載程序
U-Boot (Universal Boot Loader) 是嵌入式領域應用最廣泛的開源引導加載程序。它功能強大,通常分兩個階段執行,以適應嵌入式系統 SRAM 較小的限制。
2.1 第一階段: U-Boot SPL (Secondary Program Loader)
- SPL 是什么:一個精簡版的 U-Boot,主要目的是初始化 DDR 內存。由于 BootROM 加載 SPL 時使用的是芯片內部的 SRAM,而 SRAM 通常很小(幾十到幾百 KB),無法容納完整的 U-Boot。
- 核心職責:
- 初始化 DDR 控制器:這是 SPL 最重要的任務。DDR 內存是運行 Linux 內核和應用程序所必需的大容量內存空間。
- 加載 U-Boot 主體:DDR 初始化完成后,SPL 會從外部存儲設備中將完整的 U-Boot 鏡像 (
u-boot.img
) 加載到 DDR 內存中。 - 跳轉執行:將控制權移交給位于 DDR 中的 U-Boot 主體。
2.2 第二階段: U-Boot 主體
這是我們通常所說的 U-Boot,它功能完善,為啟動 Linux 內核提供了所有必要的準備。
- 核心職責:
- 全面的硬件初始化:初始化系統所需的各種外設,如串口(用于打印啟動日志和提供命令行交互)、網絡接口(用于網絡啟動和調試)、存儲設備(eMMC, NAND)等。
- 設置內核啟動參數 (
bootargs
):這是 U-Boot 的一個關鍵功能。它會設置一個名為bootargs
的環境變量,然后將其傳遞給 Linux 內核。這個參數字符串告訴內核一些重要的初始配置,例如:console=ttyS0,115200
:指定內核使用哪個串口作為控制臺,以及波特率。root=/dev/mmcblk0p2 rootwait
:指定根文件系統的位置(例如在 SD 卡的第 2 個分區),并讓內核等待設備就緒。rootfstype=ext4
:指定根文件系統的類型。
- 加載內核和設備樹:根據
bootcmd
環境變量中定義的命令(或手動輸入),從存儲介質(或通過網絡 TFTP)中將 Linux 內核鏡像 (zImage
) 和設備樹文件 (.dtb
) 加載到 DDR 內存的指定地址。 - 啟動內核:執行
bootz
或bootm
命令,將設備樹文件的內存地址傳遞給內核,然后跳轉到內核的入口點,正式開始 Linux 系統的啟動。
3. Linux Kernel - 操作系統的核心
內核接管控制權后,標志著真正的操作系統開始運行。
- 內核自解壓:
zImage
是一個壓縮的鏡像,所以第一步是在內存中將自己解壓出來。 - 解析設備樹 (DTB):內核會讀取 U-Boot 傳遞過來的 DTB 文件。DTB 描述了板級的所有硬件信息,內核根據這些信息來初始化對應的驅動程序。這使得內核代碼可以與硬件解耦,一份內核源碼可以適配不同的開發板。
- 初始化硬件:內核開始全面初始化由 DTB 描述的所有硬件設備,并建立起驅動模型、內存管理、進程調度等核心子系統。
- 掛載根文件系統 (RootFS):內核根據
bootargs
中的root
參數,找到并掛載根文件系統。- 在開發階段,常常使用 NFS (Network File System),方便快速修改和調試。
- 在量產產品中,根文件系統通常存放在 eMMC 或 Flash 的一個分區上,格式為
ext4
,squashfs
(只讀壓縮),ubifs
(用于 NAND Flash) 等。
- 啟動
init
進程:根文件系統被掛載后,內核會創建并運行第一個用戶空間的進程/sbin/init
(其 PID 永遠為 1)。內核的初始化工作到此結束,后續的用戶空間初始化全權交由init
進程處理。
4. Init 與用戶空間
init
進程是所有用戶空間進程的“始祖”。它的任務是根據配置文件,將系統帶入一個可用的狀態。
- 常見的
init
程序:- BusyBox init:在資源受限的嵌入式系統中非常流行。它解析
/etc/inittab
文件,按順序執行其中的腳本來啟動系統服務。 - SystemV init:傳統的
init
系統,同樣使用/etc/inittab
和運行級別 (Runlevel) 腳本 (/etc/rc.d/
)。 - systemd:現代 Linux 發行版的主流選擇,功能強大,并行啟動速度快,但在嵌入式領域有時被認為過于復雜和龐大。
- BusyBox init:在資源受限的嵌入式系統中非常流行。它解析
- 執行流程:
init
進程會運行一系列啟動腳本,這些腳本會:- 掛載其他必要的文件系統(如
/proc
,/sys
,/tmp
)。 - 配置網絡。
- 啟動系統日志、SSH 服務等后臺守護進程。
- 最終,啟動核心的應用程序。
- 掛載其他必要的文件系統(如
當用戶的應用程序成功運行起來后,整個嵌入式 Linux 系統的啟動流程便宣告完成。