引導Linux 系統的過程包括很多階段。一般的SoC 內嵌入了bootrom,上電時bootrom 運行。對於CPU0 而言,bootrom 會去引導bootloader,而其他CPU 則判斷自己是不是CPU0,進入WFI 的狀態等待CPU0 來喚醒它。CPU0 引導bootloader,bootloader 引導Linux 內核,在內核啓動階段,CPU0 會發中斷喚醒CPU1,之後CPU0 和CPU1 都投入運行。CPU0 導致用戶空間的init 程序被調用,init 程序再派生其他進程,派生出來的進程再派生其他進程。CPU0 和CPU1 共擔這些負載,進行負載均衡。
bootrom 是各個SoC 廠家根據自身情況編寫的,目前的SoC 一般都具有從SD、eMMC、NAND、USB 等介質啓動的能力,這證明這些bootrom 內部的代碼具備讀SD、NAND 等能力。
嵌入式Linux 領域最著名的bootloader 是U-Boot, 其代碼倉庫位於http://git.denx.de/u-boot.git/。早前,bootloader 需要將啓動信息以ATAG 的形式封裝,並且把ATAG 的地址填充在r2 寄存器中,機型號填充在r1 寄存器中,詳見內核文檔Documentation/arm/booting。在ARM Linux 支持設備樹(Device Tree)後,bootloader 則需要把dtb 的地址放入r2 寄存器中。
當然,ARM Linux 也支持直接把dtb 和zImage 綁定在一起的模式(內核ARM_APPENDED_DTB 選項“ Use appended device tree blob to zImage”),這樣r2 寄存器就不再需要填充dtb 地址了。
類似zImage 的內核鏡像實際上是由沒有壓縮的解壓算法和被壓縮的內核組成,所以在bootloader 跳入zImage 以後,它自身的解壓縮邏輯就把內核的鏡像解壓縮出來了。關於內核啓動,與我們關係比較大的部分是每個平臺的設備回調函數和設備屬性信息,它們通常包裝在DT_MACHINE_START 和MACHINE_END 之間, 包含reserve()、map_io()、init_machine()、init_late()、smp 等回調函數或者屬性。這些回調函數會在內核啓動過程中被調用。
用戶空間的init 程序常用的有busybox init、SysVinit、systemd 等,它們的職責類似,把整個系統啓動,最後形成一個進程樹,比如Ubuntu 上運行的pstree: