部落格:blog.focus-linux.net linuxfocus.blog.chinaunix.net
本文的copyleft歸[email protected]所有,使用GPL釋出,可以自由拷貝,轉載。但轉載請保持文檔的完整性,注明原作者及原連結,嚴禁用于任何商業用途。
======================================================================================================
無論是Linux還是Windows,在加電後的第一步都是先運作BIOS(Basic Input/Output System)程式——不知道是不是是以的電腦系統都是如此。BIOS儲存在主機闆上的一個non-volatile(即非易失)存儲器,如PROM,EPROM,Flash等。——以前的BIOS一般都是隻讀的,現代的系統中,允許重新整理BIOS程式。它的任務就是簡單的初始化和識别系統硬體裝置,如CPU,記憶體,輸入/輸出裝置,外部儲存設備等。然後找到bootloader的位置,并加載bootloader,将PC的控制權交給bootloader,完成後面的複雜的系統初始化任務。
但是在系統啟動之前,系統如何啟動BIOS呢?是以系統啟動的過程,也被稱為自舉。雖然沒有“先有雞還是先有蛋”那麼複雜,但是這裡也有一個沖突。PC是這樣解決這個問題的。将CPU設計成加電以後,就從一個特殊的固定的位址開始執行指令,那麼BIOS的位置就放在這裡,也就是存儲BIOS的ROM的起始位址就是這個固定的位址,用以保證BIOS程式可以在加電時被直接執行。
這裡有兩個問題:
1. BIOS的存儲器位址如何決定的?
2. 現在多處理器的情況下,BIOS是如何執行的?
下面以Intel CPU為例,簡單說明一下流程:
Intel在初始化的時候将CPU分為兩類,即BSP(Bootstrap Processor)與APs(Application Processors)。從名字上既可看出兩類CPU的作用。在啟動的時候,首先由硬體動态選擇一個總線上的CPU為BSP,那麼剩下的CPU則都為AP。由BSP執行BIOS程式,初始化環境以及APs,然後還是有BSP執行作業系統的初始化代碼。Intel CPU的第一條語句的固定位址為0xFFFF FFF0,然後BIOS的存儲器被hard-map到這個記憶體位址。這樣當CPU開始執行時,實際上執行的就是BIOS程式。
由于BIOS的存儲器不會太大,是以程式一般不會太複雜,那麼不大可能實作加載作業系統的操作,隻能完成簡單的初始化工作。這時,隻能借助于外部存儲器了。可是外部存儲器的讀取是依賴于檔案系統的。而BIOS程式既然比較簡單,那麼是不可能去支援檔案系統的,更何況有各種各樣的檔案系統,不可能去一一支援。這時,還是隻能依賴于寫死,必須定義一個固定的外部存儲器的位址——硬碟的第一個扇區的512位元組——被稱為MBR(Master Boot Record)——為什麼是512位元組呢?按照我的了解,一般情況下一個扇區都被設定為512位元組,而硬碟操作的最小單元即為一個扇區。雖然可以設定更大的扇區,但是作為一個統一的程式來說,使用慣例512是一個不錯的選擇。
BIOS的最後一項任務就是将MBR讀入到記憶體中,且起始位址固定為0x7c00,然後對MBR的最後兩個位元組進行驗證,必須為0x55和0xAA,以保證這512位元組為MBR。驗證通過後,則跳轉到0x7c00處開始執行。這樣MBR就開始執行。——這裡,我有兩個問題,為什麼是7c00和0x55和0xAA呢?目前沒有找到當初選擇這兩個值的解釋。我依稀記得選擇0x55,0xaa是因為這個值比較特殊,利于校驗,但是為什麼利于校驗卻不記得了。
MBR儲存了分區表(MBR并不存在于任何一個分區中,而是處于分區之上),以及一個用于裝載作業系統啟動程式的小程式。MBR首先會确定活動分區,然後使用BIOS将這個活動分區的啟動扇區——仍然是第一個扇區512位元組,最後跳轉到加載該啟動扇區的記憶體位址處。這樣就将PC的控制器轉移到這個啟動扇區的程式手中(即真正的bootloader)。一般來說,這個啟動程式也要求被加載到0x7c00這個位址。可是這個位址之前已經加載了MBR,如果再加載這個啟動程式,那麼必然沖突。是以MBR實際上在開始的時候,先對自己做了relocate,将自己拷貝到另外一個位址,然後從那個位址開始執行,這樣就避免了沖突。
下面就進入了真正的bootloader了,對于Linux來說,一般就是LILO和GRUB,下面以最常用的GRUB為例。
GRUB的啟動分為三個階段stage1,stage1.5和stage2,這三個階段也被分為三個檔案(在某些情況下,可以沒有stage1和stage1.5)。其中stage1可以嵌入到MBR中,即MBR的頭446個位元組(後面為分區表64位元組,0x55和0xAA兩個校驗位元組),也可以存儲在活動分區的第一個扇區512位元組,然後由MBR來加載。是以stage1最多為512位元組,如果存儲在MBR中,則隻能最大為446位元組。stage1中儲存了stage1.5的位址,并負責加載stage1.5的前512位元組。之是以stage1隻能加載512位元組,是為了遵循MBR的規則。
進入stage1.5,由于隻加載了前512位元組,是以stage1.5首先要負責把剩餘部分代碼,由自己加載到記憶體中。對于stage1.5來說,它可以識别和支援檔案系統。可以檢視/boot/grub目錄下,有多個字尾為stage1.5檔案,其字首即為支援的檔案系統,也就是說要支援一個檔案系統,就有一個對應的stage1.5檔案。至于加載哪個檔案,已經寫死在stage1中。這個檔案系統為stage2所在的檔案系統。stage2檔案是真正儲存在檔案系統中的。這樣通過對應的stage1.5檔案,就可以正确加載stage2檔案。為什麼會有stage1.5這個階段呢?主要是當stage2不連續或者需要在stage2前,對檔案系統做些特殊處理。如果沒有這樣的需求,完全可以避免stage1.5。
stage2檔案為最主要的加載代碼,這時由于已經stage1.5已經支援檔案系統了,是以stage2可以比較大。stage2來實作GRUB的各種功能,這裡就不列舉了,感興趣的同學可以自己檢視GRUB的手冊。stage2首先需要找到GRUB的配置檔案,來決定如何加載作業系統。對于GRUB的配置與本文的主題聯系并不緊密,我個人也對其興趣不大。
GRUB不僅要複雜加載kernel,還要負責加載Initial Ram Disk,又被成為initrd。其目的主要是為了保證一個小體積的核心。initrd為一個簡單的檔案系統,它包含了一些核心必要的檔案和子產品。這樣,首先将initrd挂載為一個根系統,然後kernel利用這個基本的系統,來檢測環境,加載更多的必要的子產品。在完成所有的加載後,這時kernel已經完全準備就緒。那麼initrd對于kernel來說,已經不需要了。這時,kernel會将initrd從根/上解除安裝,并挂載上真正的根系統,并執行正常的啟動程式。
參考:
2. 《Linux核心分析及程式設計》——倪繼利
3. 《Linux核心完全剖析》——趙炯
4. 《Linux核心源代碼情景分析》——毛德操 胡希明
5. 《Intel 64 and IA32 Architectures Software Developer‘s Manual》 Volume 3A
6. 《Understanding The Linux Kernel》 Denial P. Bovet & Macro Cesati
7. GUN GRUB Manual