1. 程式人生 > >Linux引導啟動程序 - boot

Linux引導啟動程序 - boot

ida fff 符號 搜索 選擇 概述 功能 linux 內核 stage1

主要描述 boot/目錄中的三個匯編代碼文件,見列表 3-1 所示。正如在前一章中提到的,這三個 文件雖然都是匯編程序,但卻使用了兩種語法格式。bootsect.s 和 setup.s 采用近似於 Intel 的匯編語言語法,需要使用 Intel 8086 匯編編譯器和連接器 as86 和ld86,而 head.s 則使用 GNU 的匯編程序格式,並且運行在保護模式下,需要用 GNU 的 as 進行編譯。這是一種 AT&T 語法的匯編語言程序。

使用兩種編譯器的主要原因是由於對於 Intel x86 處理器系列來講,GNU 的編譯器僅支持 i386 及以 後出的 CPU。不支持生成運行在實模式下的程序。

閱讀這些代碼除了你需要知道一些一般 8086 匯編語言的知識以外,還要對采用 Intel 80X86 微處理器的 PC 機的體系結構以及80386 32 位保護模式下的編程原理有些了解。所以在開始閱讀源代碼之前可以先大概瀏覽一下附錄中有關 PC 機硬件接口控制編程和80386 32 位保護模式的編程方法,在閱讀代碼時再就事論事地針對具體問題參考附錄中的詳細說明。

這裏先總的說明一下 Linux 操作系統啟動部分的主要執行流程。當 PC 的電源打開後,80x86 結構的 CPU 將自動進入實模式,並從地址 0xFFFF0 開始自動執行程序代碼,這個地址通常是 ROM-BIOS 中的 地址。PC 機的 BIOS 將執行某些系統的檢測

,並在物理地址 0 處開始初始化中斷向量。此後,它將可啟動設備的第一個扇區(磁盤引導扇區,512 字節)讀入內存絕對地址 0x7C00 處,並跳轉到這個地方。啟動設備通常是軟驅或是硬盤。這裏的敘述是非常簡單的,但這已經足夠理解內核初始化的工作過程了。

Linux 的最最前面部分是用 8086 匯編語言編寫的(boot/bootsect.s),它將由 BIOS 讀入到內存絕對地址 0x7C00(31KB)處,當它被執行時就會把自己移到絕對地址 0x90000(576KB)處,並把啟動設備中後 2kB字節代碼(boot/setup.s)讀入到內存 0x90200 處,而內核的其它部分(system 模塊)則被讀入到從地址0x10000 開始處,因為當時 system 模塊的長度不會超過 0x80000 字節大小(即 512KB),所以它不會覆 蓋在 0x90000 處開始的 bootsect 和 setup 模塊。後面 setup 程序將會把 system

模塊移動到內存起始處,這樣 system 模塊中代碼的地址也即等於實際的物理地址,便於對內核代碼和數據的操作。圖 3-1 清晰地顯示出 Linux 系統啟動時這幾個程序或模塊在內存中的動態位置。其中,每一豎條框代表某一時刻內存中各程序的映像位置圖。在系統加載期間將顯示信息"Loading..."。然後控制權將傳遞給 boot/setup.s 中的代碼,這是另一個實模式匯編語言程序。

啟動部分識別主機的某些特性以及 vga 卡的類型。如果需要,它會要求用戶為控制臺選擇顯示模式。 然後將整個系統從地址0x10000 移至 0x0000 處,進入保護模式跳轉至系統的余下部分(在 0x0000 處)。 此時所有 32 位運行方式的設置啟動被完成: IDT、GDT 以及 LDT 被加載,處理器和協處理器也已確認, 分頁工作也設置好了最終調用 init/main.c 中的 main()程序。上述操作的源代碼是在 boot/head.S 中的, 這可能是整個內核中最有訣竅的代碼了。註意如果在前述任何一步中出了錯,計算機就會死鎖。在操作系統還沒有完全運轉之前是處理不了出錯的。

為什麽不把系統模塊直接加載到物理地址 0x0000 開始處而要在 setup 程序中再進行移動呢?這是因 為在 setup 程序代碼開始部分還需要利用 ROM BIOS 中的中斷調用來獲取機器的一些參數(例如顯示卡 模式、硬盤參數表等)。當 BIOS 初始化時會在物理內存開始處放置一個大小為 0x400 字節(1Kb)的中斷向量表,因此需要在使用完 BIOS 的中斷調用後才能將這個區域覆蓋掉。

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 映像之外,這些引導監視器還執行一定級別的系統測試和硬件初始化過程。在嵌入式平臺中,這些引導監視器通常會涉及第一階段和第二階段的引導加載程序。

提取 MBR 的信息

要查看 MBR 的內容,請使用下面的命令:

# dd if=/dev/hda of=mbr.bin bs=512 count=1 # od -xa mbr.bin

這個 dd 命令需要以 root 用戶的身份運行,它從 /dev/hda(第一個 IDE 盤) 上讀取前 512 個字節的內容,並將其寫入 mbr.bin 文件中。od 命令會以十六進制和 ASCII 碼格式打印這個二進制文件的內容。

在 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 磁盤。

GRUB 階段引導加載程序

/boot/grub 目錄中包含了 stage1stage1.5stage2 引導加載程序,以及很多其他加載程序(例如,CR-ROM 使用的是 iso9660_stage_1_5)。

在 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 的引導加載程序就可以調用內核映像了。

內核

GRUB 中的手工引導

在 GRUB 命令行中,我們可以使用 initrd 映像引導一個特定的內核,方法如下:

grub> kernel /bzImage-2.6.14.2
[Linux-bzImage, setup=0x1400, size=0x29672e]
grub> initrd /initrd-2.6.14.2.img
[Linux-initrd @ 0x5f13000, 0xcc199 bytes]
grub> boot
Uncompressing Linux... Ok, booting the kernel.

如果您不知道要引導的內核的名稱,只需使用斜線(/)然後按下 Tab 鍵即可。GRUB 會顯示內核和 initrd 映像列表。

當內核映像被加載到內存中,並且階段 2 的引導加載程序釋放控制權之後,內核階段就開始了。內核映像並不是一個可執行的內核,而是一個壓縮過的內核映像。通常它是一個 zImage(壓縮映像,小於 512KB)或一個 bzImage(較大的壓縮映像,大於 512KB),它是提前使用 zlib 進行壓縮過的。在這個內核映像前面是一個例程,它實現少量硬件設置,並對內核映像中包含的內核進行解壓,然後將其放入高端內存中,如果有初始 RAM 磁盤映像,就會將它移動到內存中,並標明以後使用。然後該例程會調用內核,並開始啟動內核引導的過程。

當 bzImage(用於 i386 映像)被調用時,我們從 ./arch/i386/boot/head.Sstart 匯編例程開始執行(主要流程圖請參看圖 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)來掛載最終的根文件系統。

decompress_kernel 輸出

函數 decompress_kernel 就是顯示我們通常看到的解壓消息的地方:

Uncompressing Linux... Ok, booting the kernel.

Init

當內核被引導並進行初始化之後,內核就可以啟動自己的第一個用戶空間應用程序了。這是第一個調用的使用標準 C 庫編譯的程序。在此之前,還沒有執行任何標準的 C 應用程序。

在桌面 Linux 系統上,第一個啟動的程序通常是 /sbin/init。但是這不是一定的。很少有嵌入式系統會需要使用 init 所提供的豐富初始化功能(這是通過 /etc/inittab 進行配置的)。在很多情況下,我們可以調用一個簡單的 shell 腳本來啟動必需的嵌入式應用程序。

與 Linux 本身非常類似,Linux 的引導過程也非常靈活,可以支持眾多的處理器和硬件平臺。最初,加載引導加載程序提供了一種簡單的方法,不用任何花架子就可以引導 Linux。LILO 引導加載程序對引導能力進行了擴充,但是它卻缺少文件系統的感知能力。最新一代的引導加載程序,例如 GRUB,允許 Linux 從一些文件系統(從 Minix 到 Reise)上進行引導。

Linux引導啟動程序 - boot