引導 Linux® 系統的過程包括很多階段。不管您是引導一個标準的 x86 桌面系統,還是引導一台嵌入式的 PowerPC® 機器,很多流程都驚人地相似。本文将探索 Linux 的引導過程,從最初的引導到啟動第一個使用者空間應用程式。在本文介紹的過程中,您将學習到各種與引導有關的主題,例如引導加載程式、核心解壓、初始 RAM 磁盤以及 Linux 引導的其他一些元素。
早期時,啟動一台計算機意味着要給計算機喂一條包含引導程式的紙帶,或者手工使用前端面闆位址/資料/控制開關來加載引導程式。盡管目前的計算機已經裝備了很多工具來簡化引導過程,但是這一切并沒有對整個過程進行必要的簡化。
讓我們先從進階的視角來檢視 Linux 引導過程,這樣就可以看到整個過程的全貌了。然後将回顧一下在各個步驟到底發生了什麼。在整個過程中,參考一下核心源代碼可以幫助我們更好地了解核心源代碼樹,并在以後對其進行深入分析。
概述
圖 1 是我們在 20,000 英尺的高度看到的視圖。
圖 1. Linux 引導過程在 20,000 英尺處的視圖
當系統首次引導時,或系統被重置時,處理器會執行一個位于已知位置處的代碼。在個人計算機(PC)中,這個位置在基本輸入/輸出系統(BIOS) 中,它儲存在主機闆上的閃存中。嵌入式系統中的中央處理單元(CPU)會調用這個重置向量來啟動一個位于閃存/ROM 中的已知位址處的程式。在這兩種情況下,結果都是相同的。因為 PC 提供了很多靈活性,BIOS 必須确定要使用哪個裝置來引導系統。稍後我們将詳細介紹這個過程。
當找到一個引導裝置之後,第一階段的引導加載程式就被裝入 RAM 并執行。這個引導加載程式在大小上小于 512 位元組(一個扇區),其作用是加載第二階段的引導加載程式。
當第二階段的引導加載程式被裝入 RAM 并執行時,通常會顯示一個動畫螢幕,并将 Linux 和一個可選的初始 RAM 磁盤(臨時根檔案系統)加載到記憶體中。在加載映像時,第二階段的引導加載程式就會将控制權交給核心映像,然後核心就可以進行解壓和初始化了。在這個階段 中,第二階段的引導加載程式會檢測系統硬體、枚舉系統連結的硬體裝置、挂載根裝置,然後加載必要的核心子產品。完成這些操作之後啟動第一個使用者空間程式(
init
),并執行進階系統初始化工作。
這就是 Linux 引導的整個過程。現在讓我們深入挖掘一下這個過程,并深入研究一下 Linux 引導過程的一些詳細資訊。
|
系統啟動
系統啟動階段依賴于引導 Linux 系統上的硬體。在嵌入式平台中,當系統加電或重置時,會使用一個啟動環境。這方面的例子包括 U-Boot、RedBoot 和 Lucent 的 MicroMonitor。嵌入式平台通常都是與引導螢幕搭配銷售的。這些程式位于目标硬體上的閃存中的某一段特殊區域,它們提供了将 Linux 核心映像下載下傳到閃存并繼續執行的方法。除了可以存儲并引導 Linux 映像之外,這些引導螢幕還執行一定級别的系統測試和硬體初始化過程。在嵌入式平台中,這些引導螢幕通常會涉及第一階段和第二階段的引導加載程式。
|
在 PC 中,引導 Linux 是從 BIOS 中的位址 0xFFFF0 處開始的。BIOS 的第一個步驟是加電自檢(POST)。POST 的工作是對硬體進行檢測。BIOS 的第二個步驟是進行本地裝置的枚舉和初始化。
給定 BIOS 功能的不同用法之後,BIOS 由兩部分組成:POST 代碼和運作時服務。當 POST 完成之後,它被從記憶體中清理了出來,但是 BIOS 運作時服務依然保留在記憶體中,目标作業系統可以使用這些服務。
要引導一個作業系統,BIOS 運作時會按照 CMOS 的設定定義的順序來搜尋處于活動狀态并且可以引導的裝置。引導裝置可以是軟碟、CD-ROM、硬碟上的某個分區、網絡上的某個裝置,甚至是 USB 閃存。
通常,Linux 都是從硬碟上引導的,其中主引導記錄(MBR)中包含主引導加載程式。MBR 是一個 512 位元組大小的扇區,位于磁盤上的第一個扇區中(0 道 0 柱面 1 扇區)。當 MBR 被加載到 RAM 中之後,BIOS 就會将控制權交給 MBR。
|
第一階段引導加載程式
MBR 中的主引導加載程式是一個 512 位元組大小的映像,其中包含程式代碼和一個小分區表(參見圖 2)。前 446 個位元組是主引導加載程式,其中包含可執行代碼和錯誤消息文本。接下來的 64 個位元組是分區表,其中包含 4 個分區的記錄(每個記錄的大小是 16 個位元組)。MBR 以兩個特殊數字的位元組(0xAA55)結束。這個數字會用來進行 MBR 的有效性檢查。
圖 2. MBR 剖析
主引導加載程式的工作是查找并加載次引導加載程式(第二階段)。它是通過在分區表中查找一個活動分區來實作這種功能的。當找到一個活動分區時,它會 掃描分區表中的其他分區,以確定它們都不是活動的。當這個過程驗證完成之後,就将活動分區的引導記錄從這個裝置中讀入 RAM 中并執行它。
|
第二階段引導加載程式
次引導加載程式(第二階段引導加載程式)可以更形象地稱為核心加載程式。這個階段的任務是加載 Linux 核心和可選的初始 RAM 磁盤。
|
在 x86 PC 環境中,第一階段和第二階段的引導加載程式一起稱為 Linux Loader(LILO)或 GRand Unified Bootloader(GRUB)。由于 LILO 有一些缺點,而 GRUB 克服了這些缺點,是以下面讓我們就來看一下 GRUB。(有關 GRUB、LILO 和相關主題的更多内容,請參閱本文後面的 參考資料 部分的内容。)
關于 GRUB,很好的一件事情是它包含了有關 Linux 檔案系統的知識。GRUB 不像 LILO 一樣使用裸扇區,而是可以從 ext2 或 ext3 檔案系統中加載 Linux 核心。它是通過将兩階段的引導加載程式轉換成三階段的引導加載程式來實作這項功能的。階段 1 (MBR)引導了一個階段 1.5 的引導加載程式,它可以了解包含 Linux 核心映像的特殊檔案系統。這方面的例子包括
reiserfs_stage1_5
(要從 Reiser 日志檔案系統上進行加載)或
e2fs_stage1_5
(要從 ext2 或 ext3 檔案系統上進行加載)。當階段 1.5 的引導加載程式被加載并運作時,階段 2 的引導加載程式就可以進行加載了。
當階段 2 加載之後,GRUB 就可以在請求時顯示可用核心清單(在
/etc/grub.conf
中進行定義,同時還有幾個軟符号連結
/etc/grub/menu.lst
/etc/grub.conf
)。我們可以選擇核心甚至修改附加核心參數。另外,我們也可以使用一個指令行的 shell 對引導過程進行進階手工控制。
将第二階段的引導加載程式加載到記憶體中之後,就可以對檔案系統進行查詢了,并将預設的核心映像和
initrd
映像加載到記憶體中。當這些映像檔案準備好之後,階段 2 的引導加載程式就可以調用核心映像了。
|
核心
|
當核心映像被加載到記憶體中,并且階段 2 的引導加載程式釋放控制權之後,核心階段就開始了。核心映像并不是一個可執行的核心,而是一個壓縮過的核心映像。通常它是一個 zImage(壓縮映像,小于 512KB)或一個 bzImage(較大的壓縮映像,大于 512KB),它是提前使用 zlib 進行壓縮過的。在這個核心映像前面是一個例程,它實作少量硬體設定,并對核心映像中包含的核心進行解壓,然後将其放入高端記憶體中,如果有初始 RAM 磁盤映像,就會将它移動到記憶體中,并标明以後使用。然後該例程會調用核心,并開始啟動核心引導的過程。
當 bzImage(用于 i386 映像)被調用時,我們從
./arch/i386/boot/head.S
的
start
彙編例程開始執行(主要流程圖請參看圖 3)。這個例程會執行一些基本的硬體設定,并調用
./arch/i386/boot/compressed/head.S
中的
startup_32
例程。此例程會設定一個基本的環境(堆棧等),并清除 Block Started by Symbol(BSS)。然後調用一個叫做
decompress_kernel
的 C 函數(在
./arch/i386/boot/compressed/misc.c
中)來解壓核心。當核心被解壓到記憶體中之後,就可以調用它了。這是另外一個
startup_32
函數,但是這個函數在
./arch/i386/kernel/head.S
中。
在這個新的
startup_32
函數(也稱為清除程式或程序 0)中,會對頁表進行初始化,并啟用記憶體分頁功能。然後會為任何可選的浮點單元(FPU)檢測 CPU 的類型,并将其存儲起來供以後使用。然後調用
start_kernel
函數(在
init/main.c
中),它會将您帶入與體系結構無關的 Linux 核心部分。實際上,這就是 Linux 核心的
main
函數。
圖 3. Linux 核心 i386 引導的主要函數流程
通過調用
start_kernel
,會調用一系列初始化函數來設定中斷,執行進一步的記憶體配置,并加載初始 RAM 磁盤。最後,要調用
kernel_thread
(在
arch/i386/kernel/process.c
中)來啟動
init
函數,這是第一個使用者空間程序(user-space process)。最後,啟動空任務,現在排程器就可以接管控制權了(在調用
cpu_idle
之後)。通過啟用中斷,搶占式的排程器就可以周期性地接管控制權,進而提供多任務處理能力。
在核心引導過程中,初始 RAM 磁盤(
initrd
)是由階段 2 引導加載程式加載到記憶體中的,它會被複制到 RAM 中并挂載到系統上。這個
initrd
會作為 RAM 中的臨時根檔案系統使用,并允許核心在沒有挂載任何實體磁盤的情況下完整地實作引導。由于與外圍裝置進行互動所需要的子產品可能是
initrd
的一部分,是以核心可以非常小,但是仍然需要支援大量可能的硬體配置。在核心引導之後,就可以正式裝備根檔案系統了(通過
pivot_root
):此時會将
initrd
根檔案系統解除安裝掉,并挂載真正的根檔案系統。
|
initrd
函數讓我們可以建立一個小型的 Linux 核心,其中包括作為可加載子產品編譯的驅動程式。這些可加載的子產品為核心提供了通路磁盤和磁盤上的檔案系統的方法,并為其他硬體提供了驅動程式。由于根檔案系統是磁盤上的一個檔案系統,是以
initrd
函數會提供一種啟動方法來獲得對磁盤的通路,并挂載真正的根檔案系統。在一個沒有硬碟的嵌入式環境中,
initrd
可以是最終的根檔案系統,或者也可以通過網絡檔案系統(NFS)來挂載最終的根檔案系統。
|
Init
當核心被引導并進行初始化之後,核心就可以啟動自己的第一個使用者空間應用程式了。這是第一個調用的使用标準 C 庫編譯的程式。在此之前,還沒有執行任何标準的 C 應用程式。
|