一個Linux®系統的boot包括了幾個階段。但不論是x86上的標準桌面仍是一個嵌入式的PowerPC® ,大部分系統的boot流程都是徹底相似的。這篇日誌會深刻解釋從最初的bootstrap到第一個用戶程序運行起來的整個過程。你會看到全部的與boot過程相關的概念,如boot loader,kernel decompression,initial RAM disk等等。php
在古代(固然,是計算機的古代),啓動一個計算機一般使用包含啓動程序的打孔紙帶載入或者是手動從前置面板上的地址、數據、控制開關上手動加載啓動程序。咱們如今使用的計算機使用了多種手段來簡化啓動過程,可是整個啓動過程實質上並非看起來那麼簡單。html
在下面的部分,咱們先從整體上看看Linux的啓動過程,作到心中有數。而後再對每一個步驟進行深刻的解釋。結合後面給出的參考代碼,就能夠對整個kernel的相關部分有比較深入的理解了。linux
概覽ios
下圖能夠給你一個概覽(比例尺大概是1:20,000,真的是很概…覽)shell
Figure 1. Linux啓動過程概覽
bootstrap
當系統首次啓動或重啓時,處理器將執行一段特殊位置上的代碼:對於PC機而言,是在主板的flash片子上保存的基本輸入輸出系統(BIOS),對於嵌入式系統,則是由CPU激活一個reset vector,啓動flash或者ROM裏面的一段特定程序。兩種類型的過程是類似的,只是對於PC來講,哪些設備將會在啓動過程當中被使用有至關的靈活性(能夠從硬盤、光盤、USB設備等等),所以須要由BIOS來選取設備。這裏面的細節後面會講到。網絡
當啓動使用的設備找到以後,所謂的first-stage boot loader就加載到RAM中執行了。這個boot loader的長度小於512bytes(一個磁盤的sector大小,記得嗎?),它的任務就是加載後面的second-stage boot loader。app
當所謂的second-stage boot loader加載並執行以後,Linux和一個可選的初始化RAM盤(臨時的根文件系統)就被載入到內存中。而後控制權就從該boot loader轉交給kernel映像。而後,kernel解壓並初始化。這個過程當中,boot loader檢查系統上掛載的硬件,mount好root設備,並加載好必需的kernel module。完成這些工做以後,第一個user-space的程序(init)就開始運行,進行更高級別的初始化工做。ide
這些就是一個簡要的linux系統啓動過程。下面咱們深刻到每一個部分。函數
系統啓動時的動做和Linux最終運行的硬件平臺有關。對於嵌入式系統而言,一個bootstrap環境,如U-Boot、RedBoot、MicroMonitor等,會在系統上電或復位後運行。具體來講,和嵌入式系統一塊兒發佈的flash特殊位置中的boot monitor會在上電或復位後,將Linux kernel映像加載到flash中,而後執行它。同時,這個boot monitor還會負責一部分系統測試和硬件初始化工做。也就是說嵌入式環境中,一個boot monitor會執行兩個stage的boot loader的任務。
在PC中,
加載Linux是從BIOS的0xFFFF0地址開始的。首先咱們須要知道,BIOS中的程序按功能不一樣分爲兩類,一類是用來POST(power-on self test),即上電的硬件自檢用的。一類用來提供實時服務。Linux在PC上的加載,第一步就是完成POST,接着這部分代碼會從內存中沖掉,而服務模塊會繼續保留來爲將要加載的系統服務。在加載的第二步,即本地設備的自檢和初始化中,正是服務模塊對CMOS中設定的提供啓動服務的設備進行掃描。可用來提供啓動服務的設備包括了軟盤、CD-ROM、硬盤、USB和網絡設備等等,可是最經常使用的仍是從硬盤啓動。
硬盤上,會有一個位於硬盤的第一個sector(也就是cylinder 0,head 0的sector1)的叫MBR(Master Boot Record)的地方,保存着primary boot loader。一旦這個boot loader加載到RAM,BIOS就把控制權交給它。
使用下面的命令能夠查看MBR的內容:
# dd if=/dev/hda of=mbr.bin bs=512 count=1
# od -xa mbr.bin
命令dd必須由root執行,
它讀取/dev/hda(瞭解linux的朋友應該知道,這是系統上的第一個IDE設備)中的第一個512bytes,而後把它寫到mbr.bin文件中。命令od則是按照hex和ASCII兩種格式把這個二進制文件打印出來。
這時在內存中運行的從MBR搞來的Primary boot loader,包括了程序代碼和分區表兩個部分,如圖2所示。512bytes中的前446bytes用來放loader,其中既有可執行代碼也有錯誤消息文件。接下來的64bytes是四個分區表,每一個16bytes。最後是兩個bytes的magic number(其實就是0xAA55),主要是用來校驗這個MBR是否是有效。
Primary boot loader的主要做用無非是把secondary boot loader (stage 2)加載進內存:它會順序查看各個分區,當找到一個活動的分區時,它會檢查一下其餘的分區狀態是否是都不是活動的。肯定只有一個活動的分區後,該分區的boot record就會從設備上拷貝到RAM中執行。
Secondary或者說second-stage的boot loader其實就負責加載Linux kernel等。
咱們經常把first-stage和second-stage的boot loaders合稱爲Linux Loader(LILO)或是x86 PC環境下的GRand Unified Bootloader (GRUB)。因爲LILO有一些GRUB不具有的優勢,因此大部分的時候咱們是使用GRUB,所以主要討論它 (你能夠在本文後面的Resources部分找到更多關於GRUB、LILO等相關內容的資料) 。
GRUB的舒爽之處在於它能讀懂Linux的文件系統,因而咱們能夠從ext2或者ext3文件系統加載Linux Kernel,而不像LILO那樣,要從原始的sector中加載。實際上,GRUB是把前面說的兩個stage的boot loader擴展到三個階段,也就是在stage1以後,加入了stage1.5 boot loader,來完成對文件系統的認知的。好比reiserfs_stage1_5
(從一個 Reiser文件系統)或者e2fs_stage1_5
(從ext2或者ext3文件系統)加載。當這個stage 1.5 boot loader加載運行以後,stage 2 boot loader才加載。
/boot/grub
目錄下保存着stage1
,stage1.5
, 和stage2
的boot loaders,以及不少其餘的可選loader (好比供CR-ROMs使用的iso9660_stage_1_5
)。
當stage 2 加載完畢,GRUB能夠列出可用的kernel(在/etc/grub.conf中定義
)。 你能夠選擇其中的一個,而且設置你選中的kernel的啓動參數。若是你知道怎麼寫shell的話,顯然能夠經過GRUB更精確的控制整個啓動過程的細節。
當second-stage的boot loader加載到內存後,默認的kernel映像和initrd
映像會加載到內存中。這些映像加載完畢以後,second-stage的boot loader就會激活kernel映像。
在GRUB的命令行下,你能夠引導一個特定的kernel和特定名稱的
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.
若是你不知道將要用於加載的kernel的名稱,能夠在GRUB命令行中用/+Tab命令列出可用的kernel和initrd映像。
當kernel映像加載到內存中,並從stage 2的boot loader手中接過了控制權以後,咱們要知道,這個kernel通常還不是一個可執行的kernel,而是壓縮過的kernel映像。一般這個映像使用zlib壓縮爲zImage (compressed p_w_picpath,小於512KB) 或者是bzImage (big compressed p_w_picpath,大於512KB)。在這個映像的初始部分是一個小模塊,進行一些基本的硬件初始化工做,而後把可執行的kernel部分解壓出來,放到內存高位。接下來,這個模塊就調用kernel,開始kernel引導工做。
以一個i386的映像爲例,這個bzImage被激活後,會從位於./arch/i386/boot/head.S中的
start函數
開始執行(圖三是基本步驟的流程圖)。這個函數進行一些基本的硬件初始化,而後就調用./arch/i386/boot/compressed/head.S的
(堆棧等),清空Block Started by Symbol (BSS)。接着startup_32函數。此函數配置基本環境
./arch/i386/boot/compressed/misc.c
中的decompress_kernel函數被調用,kernel解壓到內存中。
./arch/i386/kernel/head.S的另外一個
startup_32函數在解壓完成以後獲得調用。這個函數
(也被稱爲swapper或者是process 0)完成分頁表初始化,內存分頁功能就緒。同時CPU類型和可用FPU狀況被檢測並保存起來,供後續使用。接下來位於init/main.c中的
Linux kernel的main函數。start_kernel函數被調用,這個函數從本質上能夠被看做
Figure 3. Linux kernel i386 啓動流程中的函數調用
調用start_kernel會激活一系列的初始化函數,進行中斷設置,內存設置和RAM初始化等工做。此後,位於
arch/i386/kernel/process.c文件中的
kernel_thread被調用,
init函數隨之運行。這是第一個用戶空間的進程。最終,idle任務開始運行,調度函數得到控制權(這是在調用
cpu_idle以後
)。
在整個kernel的啓動中,在stage 2的boot loader載入到內存中的initial-RAM disk (initrd
) 會被拷貝到RAM中,並掛載起來。它以一個臨時的文件系統的身份在RAM中工做,使得kernel在沒有任何物理設備掛載的狀況下也能夠完整的啓動起來。正是因爲全部與外圍設備相關的交互均可以放到initrd中,kernel自己雖然很小,但卻支持範圍極其廣的硬件設備。在kernel的啓動完成以後,root文件系統就會回滾(經過
pivot_root
),initrd的
root文件系統被卸載,實際的root文件系統被掛載起來。
kernel的啓動和初始化完成後,kernel開始第一個用戶空間程序。這也是整個過程當中激活的第一個用標準C庫編譯的程序。
對於一個桌面Linux系統而言,通常來講啓動後運行的第一個程序是/sbin/init。但這顯然不是必須的:不少嵌入式系統不須要init提供的那麼多功能(看看/etc/inittab就知道有多複雜了),因此你能夠運行一個簡單的腳本啓動嵌入式程序。
從上面的敘述咱們知道,和Linux系統自己同樣,Linux的啓動過程是很是靈活的。最初的loadlin boot loader提供了簡便的啓動linux的可能。接下來的LILO boot loader擴展了啓動功能,可是沒有區分文件系統的能力。最新一代的boot loaders,如GRUB等,則支持從一系列文件系統(Minix到Reiser等)來啓動Linux的功能。
學習資料
- Boot Records Revealed ,學習MBRs的好地方,同時提供了大量的boot loaders。同時,這裏還討論GRUB, LILO和各類版本的Windows boot loader。
- Disk Geometry是學習硬盤相關技術的好地方,它有一個不錯的硬盤參數列表。
- live CD,玩過Linux的兄弟們應該再熟悉不過了。它是一個能夠從CD或者DVD上引導的操做系統,你甚至不須要有硬盤。
- 「Boot loader showdown: Getting to know LILO and GRUB」 (developerWorks, August 2005) 描述了LILO和GRUB boot loader的不少細節。
- Linux Professional Institute (LPI) exam prep系列文章討論了Linux的啓動,同時還討論了不少基礎的Linux任務,若是你想成爲Linux系統管理員,值得一看。
- LILO能夠算是GRUB的…可是你仍然能夠看到用它引導Linux的例子。
- mkintrd命令用來建立initial RAM disk p_w_picpath。
- 在Debian Linux Kernel Project中,能夠找到不少關於Linux的kernel,boot和嵌入式開發的技術內容。
產品技術
- MicroMonitor 提供了多種目標環境的boot環境支持。你可使用它在嵌入式環境中啓動Linux系統。它支持ARM,XScale,MIPS,PowerPC,Coldfire和Hitachi’s Super-H。
- GNU GRUB,可選項極其豐富的boot shell。
- LinuxBIOS ,其實它就是一個壓縮後的kernel。
- OpenBIOS 也是一個跨平臺可移植的BIOS項目。支持平臺包括了x86, Alpha和AMD64。
- kernel.org那邊能夠看到最新的kernel樹。