如圖所示爲 X86 PC 上從上電/復位到運行 Linux 用戶空間初始進程的流程。在進入與 Linux相關代碼之間,會經歷以下階段。shell
( 1 ) 當系統上電或復位時, CPU 會將 PC 指針賦值爲一個特定的地址 0xFFFF0 並執行該地址處的指令。在 PC 機中,該地址位於 BIOS 中,它保存在主板上的 ROM 或 Flash 中。 網絡
( 2) BIOS 運行時按照 CMOS 的設置定義的啓動設備順序來搜索處於活動狀態而且能夠引導的設備。 若從硬盤啓動, BIOS 會將硬盤 MBR(主引導記錄)中的內容加載到 RAM。MBR 是一個 512 字節大小的扇區,位於磁盤上的第一個扇區中(0 道 0 柱面 1 扇區)。當 MBR 被加載到 RAM中以後, BIOS 就會將控制權交給 MBR。
( 3) 主引導加載程序查找並加載次引導加載程序。它在分區表中查找活動分區,當找到一個活動分區時,掃描分區表中的其餘分區,以確保它們都不是活動的。當這個過程驗證完成以後,就將活動分區的引導記錄從這個設備中讀入 RAM 中並執行它。
( 4) 次引導加載程序加載 Linux 內核和可選的初始 RAM 磁盤,將控制權交給 Linux 內核源代碼。函數
( 5) 運行被加載的內核,並啓動用戶空間應用程序。工具
嵌入式系統中 Linux 的引導過程與之相似,但通常更加簡潔。不論具體以怎樣的方式實現,只要具有以下特徵就能夠稱其爲 Bootloader。spa
! 能夠在系統上電或復位的時候以某種方式執行,這些方式包括被 BIOS 引導執行、直接在 NOR Flash 中執行、 NAND Flash 中的代碼被 MCU 自動拷入內部或外部 RAM 執行等。
! 能將 U 盤、磁盤、光盤、 NOR/NAND Flash、 ROM、 SD 卡等存儲介質,甚或網口、串口中的操做系統加載到 RAM 並把控制權交給操做系統源代碼執行。操作系統
完成上述功能的 Bootloader 的實現方式很是多樣化,甚至自己也能夠是一個簡化版的操做系統。著名的 Linux Bootloader 包括應用於 PC 的 LILO 和 GRUB,應用於嵌入式系統的 U-Boot、RedBoot 等。
相比較於 LILO, GRUB 自己能理解 EXT二、 EXT3 文件系統, 所以可在文件系統中加載 Linux,而 LILO 只能識別" 裸扇區"。U-Boot 的定位爲" Universal Bootloader",其功能比較強大,涵蓋了包括 PowerPC、命令行
ARM、MIPS 和 X86 在內的絕大部分處理器構架,提供網卡、串口、 Flash 等外設驅動,提供必要的網絡協議( BOOTP、 DHCP、 TFTP),能識別多種文件系統( cramfs、 fat、 jffs2 和 registerfs 等),並附帶了調試、腳本、引導等工具,應用十分普遍。Redboot 是 Redhat 公司隨 eCos 發佈的 Bootloader 開源項目,除了包含 U-Boot 相似的強大功能外,它還包含 GDB stub(插樁),所以能經過串口或網口與 GDB 進行通訊,調試 GCC 產生的任何程序(包括內核)。咱們有必要對上述流程的第 5 個階段進行更詳細的分析,它完成啓動內核並運行用戶空間的init 進程。當內核映像被加載到 RAM 以後, Bootloader 的控制權被釋放,內核階段就開始了。內核映像並非徹底可直接執行的目標代碼,而是一個壓縮過的 zImage(小內核)或 bzImage(大內核,bzImage 中的 b 是" big"的意思)。
可是,並不是 zImage 和 bzImage 映像中的一切都被壓縮了,不然 Bootloader 把控制權交給這個內核映像它就" 傻" 了。實際上,映像中包含未被壓縮的部分,這部分中包含解壓縮程序,解壓縮程序會解壓映像中被壓縮的部分。線程
zImage 和 bzImage 都是用 gzip 壓縮的,它們不只是一個壓縮文件,並且在這兩個文件的開頭部份內嵌有 gzip 解壓縮代碼。
如圖所示,當 bzImage(用於 i386 映像)被調用時,它從/arch/i386/boot/head.S 的 start 彙編例程開始執行。這個程序執行一些基本的硬件設置,並調用/arch/i386/boot/compressed/head.S 中的startup_32 例程。 startup_32 程序設置一些基本的運行環境(如堆棧)後,清除 BSS 段,調用/arch/i386/boot/compressed/misc.c 中的 decompress_kernel() C 函數解壓內核。內核被解壓到內存中以後,會再調用 /arch/i386/kernel/head.S 文件中 的 startup_32 例程,這個新的 startup_32 例程(稱爲清除程序或進程 0)會初始化頁表,並啓用內存分頁機制,接着爲任何可選的浮點單元( FPU)檢測
CPU 的類型,並將其存儲起來供之後使用。這些都作完以後, /init/main.c 中的 start_kernel()函數被調用,進入與體系結構無關的 Linux 內核部分。start_kernel()會調用一系列初始化函數來設置中斷,執行進一步的內存配置。以後, /arch/i386/kernel/process.c 中 kernel_thread()被調用以啓動第一個核心線程,該線程執行 init()函數,而原執行序列會調用 cpu_idle()等待調度。做爲核心線程的 init()函數完成外設及其驅動程序的加載和初始化,掛接根文件系統。 init()打開/dev/console 設備,重定向 stdin、 stdout 和 stderr 到控制檯。以後,它搜索文件系統中的 init程序(也能夠由" init=" 命令行參數指定 init 程序),並使用 execve()系統調用執行 init 程序。搜索 init 程序的順序爲: /sbin/init、 /etc/init、 /bin/init 和/bin/sh。在嵌入式系統中,多數狀況下,能夠給內核傳入一個簡單的 shell 腳原本啓動必需的嵌入式應用程序。至此,漫長的 Linux 內核引導和啓動過程就此結束,而 init()對應的這個由 start_kernel()建立的第一個線程也進入用戶模式。 3d