無論是Linux還是Windows,在加電后的第一步都是先運行BIOS(Basic Input/Output System)程序——不知道是不是所以的電腦系統(tǒng)都是如此。BIOS保存在主板上的一個non-volatile(即非易失)存儲器,如PROM,EPROM,F(xiàn)lash等。——以前的BIOS一般都是只讀的,現(xiàn)代的系統(tǒng)中,允許刷新BIOS程序。它的任務(wù)就是簡單的初始化和識別系統(tǒng)硬件設(shè)備,如CPU,內(nèi)存,輸入/輸出設(shè)備,外部存儲設(shè)備等。然后找到bootloader的位置,并加載bootloader,將PC的控制權(quán)交給bootloader,完成后面的復(fù)雜的系統(tǒng)初始化任務(wù)。
但是在系統(tǒng)啟動之前,系統(tǒng)如何啟動BIOS呢?所以系統(tǒng)啟動的過程,也被稱為自舉。雖然沒有“先有雞還是先有蛋”那么復(fù)雜,但是這里也有一個矛盾。PC是這樣解決這個問題的。將CPU設(shè)計成加電以后,就從一個特殊的固定的地址開始執(zhí)行指令,那么BIOS的位置就放在這里,也就是存儲BIOS的ROM的起始地址就是這個固定的地址,用以保證BIOS程序可以在加電時被直接執(zhí)行。
這里有兩個問題:
1. BIOS的存儲器地址如何決定的?
2. 現(xiàn)在多處理器的情況下,BIOS是如何執(zhí)行的?
下面以Intel CPU為例,簡單說明一下流程:
Intel在初始化的時候?qū)PU分為兩類,即BSP(Bootstrap Processor)與APs(Application Processors)。從名字上既可看出兩類CPU的作用。在啟動的時候,首先由硬件動態(tài)選擇一個總線上的CPU為BSP,那么剩下的CPU則都為AP。由BSP執(zhí)行BIOS程序,初始化環(huán)境以及APs,然后還是有BSP執(zhí)行操作系統(tǒng)的初始化代碼。Intel CPU的第一條語句的固定地址為0xFFFF FFF0,然后BIOS的存儲器被hard-map到這個內(nèi)存地址。這樣當CPU開始執(zhí)行時,實際上執(zhí)行的就是BIOS程序。
由于BIOS的存儲器不會太大,所以程序一般不會太復(fù)雜,那么不大可能實現(xiàn)加載操作系統(tǒng)的操作,只能完成簡單的初始化工作。這時,只能借助于外部存儲器了。可是外部存儲器的讀取是依賴于文件系統(tǒng)的。而BIOS程序既然比較簡單,那么是不可能去支持文件系統(tǒng)的,更何況有各種各樣的文件系統(tǒng),不可能去一一支持。這時,還是只能依賴于硬編碼,必須定義一個固定的外部存儲器的地址——硬盤的第一個扇區(qū)的512字節(jié)——被稱為MBR(Master Boot Record)——為什么是512字節(jié)呢?按照我的理解,一般情況下一個扇區(qū)都被設(shè)置為512字節(jié),而硬盤操作的最小單元即為一個扇區(qū)。雖然可以設(shè)置更大的扇區(qū),但是作為一個統(tǒng)一的程序來說,使用慣例512是一個不錯的選擇。
BIOS的最后一項任務(wù)就是將MBR讀入到內(nèi)存中,且起始地址固定為0x7c00,然后對MBR的最后兩個字節(jié)進行驗證,必須為0x55和0xAA,以保證這512字節(jié)為MBR。驗證通過后,則跳轉(zhuǎn)到0x7c00處開始執(zhí)行。這樣MBR就開始執(zhí)行。——這里,我有兩個問題,為什么是7c00和0x55和0xAA呢?目前沒有找到當初選擇這兩個值的解釋。我依稀記得選擇0x55,0xaa是因為這個值比較特殊,利于校驗,但是為什么利于校驗卻不記得了。
MBR保存了分區(qū)表(MBR并不存在于任何一個分區(qū)中,而是處于分區(qū)之上),以及一個用于裝載操作系統(tǒng)啟動程序的小程序。MBR首先會確定活動分區(qū),然后使用BIOS將這個活動分區(qū)的啟動扇區(qū)——仍然是第一個扇區(qū)512字節(jié),最后跳轉(zhuǎn)到加載該啟動扇區(qū)的內(nèi)存地址處。這樣就將PC的控制器轉(zhuǎn)移到這個啟動扇區(qū)的程序手中(即真正的bootloader)。一般來說,這個啟動程序也要求被加載到0x7c00這個地址。可是這個地址之前已經(jīng)加載了MBR,如果再加載這個啟動程序,那么必然沖突。所以MBR實際上在開始的時候,先對自己做了relocate,將自己拷貝到另外一個地址,然后從那個地址開始執(zhí)行,這樣就避免了沖突。
下面就進入了真正的bootloader了,對于Linux來說,一般就是LILO和GRUB,下面以最常用的GRUB為例。
GRUB的啟動分為三個階段stage1,stage1.5和stage2,這三個階段也被分為三個文件(在某些情況下,可以沒有stage1和stage1.5)。其中stage1可以嵌入到MBR中,即MBR的頭446個字節(jié)(后面為分區(qū)表64字節(jié),0x55和0xAA兩個校驗字節(jié)),也可以存儲在活動分區(qū)的第一個扇區(qū)512字節(jié),然后由MBR來加載。所以stage1最多為512字節(jié),如果存儲在MBR中,則只能最大為446字節(jié)。stage1中保存了stage1.5的地址,并負責加載stage1.5的前512字節(jié)。之所以stage1只能加載512字節(jié),是為了遵循MBR的規(guī)則。
進入stage1.5,由于只加載了前512字節(jié),所以stage1.5首先要負責把剩余部分代碼,由自己加載到內(nèi)存中。對于stage1.5來說,它可以識別和支持文件系統(tǒng)。可以查看/boot/grub目錄下,有多個后綴為stage1.5文件,其前綴即為支持的文件系統(tǒng),也就是說要支持一個文件系統(tǒng),就有一個對應(yīng)的stage1.5文件。至于加載哪個文件,已經(jīng)硬編碼在stage1中。這個文件系統(tǒng)為stage2所在的文件系統(tǒng)。stage2文件是真正保存在文件系統(tǒng)中的。這樣通過對應(yīng)的stage1.5文件,就可以正確加載stage2文件。為什么會有stage1.5這個階段呢?主要是當stage2不連續(xù)或者需要在stage2前,對文件系統(tǒng)做些特殊處理。如果沒有這樣的需求,完全可以避免stage1.5。
stage2文件為最主要的加載代碼,這時由于已經(jīng)stage1.5已經(jīng)支持文件系統(tǒng)了,所以stage2可以比較大。stage2來實現(xiàn)GRUB的各種功能,這里就不列舉了,感興趣的同學可以自己查看GRUB的手冊。stage2首先需要找到GRUB的配置文件,來決定如何加載操作系統(tǒng)。對于GRUB的配置與本文的主題聯(lián)系并不緊密,我個人也對其興趣不大。
GRUB不僅要復(fù)雜加載kernel,還要負責加載Initial Ram Disk,又被成為initrd。其目的主要是為了保證一個小體積的內(nèi)核。initrd為一個簡單的文件系統(tǒng),它包含了一些內(nèi)核必要的文件和模塊。這樣,首先將initrd掛載為一個根系統(tǒng),然后kernel利用這個基本的系統(tǒng),來檢測環(huán)境,加載更多的必要的模塊。在完成所有的加載后,這時kernel已經(jīng)完全準備就緒。那么initrd對于kernel來說,已經(jīng)不需要了。這時,kernel會將initrd從根/上卸載,并掛載上真正的根系統(tǒng),并執(zhí)行正常的啟動程序。