BIOS下啟動Linux的過程
稍有計算機基礎的人都應該聽過BIOS(Basic Input / Output System),又稱基本輸入輸出系統,可以視為是一個永久地記錄在ROM中的一個軟體,是作業系統輸入輸出管理系統的一部分。早期的BIOS晶片確實是”只讀”的,裡面的內容是用一種燒錄器寫入的,一旦寫入就不能更改,除非更換晶片。現在的主機板都使用一種叫Flash EPROM的晶片來儲存系統BIOS,裡面的內容可通過使用主機板廠商提供的擦寫程式擦除後重新寫入,這樣就給使用者升級BIOS提供了極大的方便。
BIOS的功能由兩部分組成,分別是POST碼和Runtime服務。POST階段完成後它將從儲存器中被清除,而Runtime服務會被一直保留,用於目標作業系統的啟動。BIOS兩個階段所做的詳細工作如下:
步驟1:上電自檢POST(Power-on self test),主要負責檢測系統外圍關鍵裝置(如:CPU、記憶體、顯示卡、I/O、鍵盤滑鼠等)是否正常。例如,最常見的是記憶體鬆動的情況,BIOS自檢階段會報錯,系統就無法啟動起來;
步驟2:步驟1成功後,便會執行一段小程式用來列舉本地裝置並對其初始化。這一步主要是根據我們在BIOS中設定的系統啟動順序來搜尋用於啟動系統的驅動器,如硬碟、光碟、U盤、軟盤和網路等。我們以硬碟啟動為例,BIOS此時去讀取硬碟驅動器的第一個扇區(MBR,512位元組),然後執行裡面的程式碼。實際上這裡BIOS並不關心啟動裝置第一個扇區中是什麼內容,它只是負責讀取該扇區內容、並執行。
至此,BIOS的任務就完成了,此後將系統啟動的控制權移交到MBR部分的程式碼。
PS: 在個人電腦中,Linux的啟動是從0xFFFF0地址開始的。
- 系統引導
磁碟分割槽表包含以下三部分:
1)、Partition ID (5:延申 82:Swap 83:Linux 8e:LVM fd:RAID)
2)、Partition起始磁柱
3)、Partition的磁柱數量
通常情況下,諸如lilo、grub這些常見的載入程式都直接安裝在MBR中。我們以grub為例來分析這個引導過程。
grub引導也分為兩個階段stage1階段和stage2階段(有些較新的grub又定義了stage1.5階段)。
1)、stage1:stage1是直接被寫入到MBR中去的,這樣機器一啟動檢測完硬體後,就將控制權交給了GRUB的程式碼。也就是上圖所看到的前446個位元組空間中存放的是stage1的程式碼。BIOS將stage1載入記憶體中0x7c00處並跳轉執行。stage1(/stage1/start.S)的任務非常單純,僅僅是將硬碟0頭0道2扇區讀入記憶體。而0頭0道2扇區內容是原始碼中的/stage2/start.S,編譯後512位元組,它是stage2或者stage1_5的入口。而此時,stage1是沒有識別檔案系統的能力的。如果感覺腦子有些暈了,那麼下面的過程就直接跳過,去看stage2吧!
【外傳】定位硬碟的0頭0道2扇區的過程:
BIOS將stage1載入記憶體0x7c00處並執行,然後呼叫BIOS INIT13中斷,將硬碟0頭0道2扇區內容載入記憶體0x7000處,然後呼叫copy_buffer將其轉移到記憶體0x8000處。在定位0頭0道2扇區時通常有兩種定址方式:LBA和CHS。如果你是刨根問底兒型的愛好者,那麼此時去找谷哥打聽打聽這兩種方式的來龍去脈吧。
2)、stage2:嚴格來說這裡還應該再區分個stage1.5的,就一併把stage1.5放在這裡一起介紹了,免得大家看得心裡亂哄哄的。好的,我們繼續說0頭0到2扇區的/stage2/start.S檔案,當它的內容被讀入到記憶體之後,它的主要作用就是負責將stage2或stage1.5從硬碟讀到記憶體中。如果是stage2,它將被載入到0x820處;如果是stage1.5,它將被載入到0x2200處。這裡的stage2或者stage1_5不是/boot分割槽/boot/grub目錄下的檔案,因為這個時候grub還沒有能力識別任何檔案系統。
? 如果start.S載入stage1.5:stage1.5它存放在硬碟0頭0道3扇區向後的位置,stage1_5作為stage1和stage2中間的橋樑,stage1_5有識別檔案系統的能力,此後grub才有能力去訪問/boot分割槽/boot/grub目錄下的 stage2檔案,將stage2載入記憶體並執行。
? 如果start.S載入stage2:同樣,這個stage2也不是/boot分割槽/boot/grub目錄下的stage2,這個時候start.S讀取的是存放在/boot分割槽Boot Sector的stage2。這種情況下就有一個限制:因為start.S通過BIOS中斷方式直接對硬碟定址(而非通過訪問具體的檔案系統),其定址範圍有限,限制在8GB以內。因此這種情況需要將/boot分割槽分在硬碟8GB定址空間之前。
假如是情形2,我們將/boot/grub目錄下的內容清空,依然能成功啟動grub;假如是情形1,將/boot/grub目錄下stage2刪除後,則系統啟動過程中grub會啟動失敗。
- 啟動核心
當stage2被載入記憶體執行時,它首先會去解析grub的配置檔案/boot/grub/grub.conf,然後載入核心映象到記憶體中,並將控制權轉交給核心。而核心會立即初始化系統中各裝置並做相關的配置工作,其中包括CPU、I/O、儲存裝置等。
關於Linux的裝置驅動程式的載入,有一部分驅動程式直接被編譯進核心映象中,另一部分驅動程式則是以模組的形式放在initrd(ramdisk)中。
Linux核心需要適應多種不同的硬體架構,但是將所有的硬體驅動編入核心又是不實際的,而且核心也不可能每新出一種硬體結構,就將該硬體的裝置驅動寫入核心。實際上Linux的核心映象僅是包含了基本的硬體驅動,在系統安裝過程中會檢測系統硬體資訊,根據安裝資訊和系統硬體資訊將一部分裝置驅動寫入 initrd 。這樣在以後啟動系統時,一部分裝置驅動就放在initrd中來載入。這裡有必要給大家再多介紹一下initrd這個東東:
initrd 的英文含義是 bootloader initialized RAM disk,就是由 boot loader 初始化的記憶體盤。在 linu2.6核心啟動前,boot loader 會將儲存介質中的 initrd 檔案載入到記憶體,核心啟動時會在訪問真正的根檔案系統前先訪問該記憶體中的 initrd 檔案系統。在 boot loader 配置了 initrd 的情況下,核心啟動被分成了兩個階段,第一階段先執行 initrd 檔案系統中的init,完成載入驅動模組等任務,第二階段才會執行真正的根檔案系統中的 /sbin/init 程序。
另外一個概念:initramfs
initramfs 是在 kernel 2.5中引入的技術,實際上它的含義就是:在核心映象中附加一個cpio包,這個cpio包中包含了一個小型的檔案系統,當核心啟動時,核心將這個 cpio包解開,並且將其中包含的檔案系統釋放到rootfs中,核心中的一部分初始化程式碼會放到這個檔案系統中,作為使用者層程序來執行。這樣帶來的明顯的好處是精簡了核心的初始化程式碼,而且使得核心的初始化過程更容易定製。
疑惑的是:我的核心是2.6.32-71.el6.i686版本,但在我的/boot分割槽下面卻存在的是/boot/initramfs-2.6.32-71.el6.i686.img型別的檔案,沒搞明白,還望高人解惑。我只知道在2.6核心中支援兩種格式的initrd,一種是2.4核心的檔案系統映象image-initrd,一種是cpio格式。接下來我們就來探究一下initramfs-2.6.32-71.el6.i686.img裡到底放了那些東西。
在tmp資料夾中解壓initrd.img裡的內容:
如果initrd.img檔案的格式顯示為“initrd.img:ISO 9660 CD-ROM filesystem data”,則可直接輸入命令“mount -o loop initrd.img /mnt/test”進行掛載。
通過上的分析和我們的驗證,我們確實得到了這樣的結論:
grub的stage2將initrd載入到記憶體裡,讓後將其中的內容釋放到內容中,核心便去執行initrd中的init指令碼,這時核心將控制權交給了init檔案處理。我們簡單瀏覽一下init指令碼的內容,發現它也主要是載入各種儲存介質相關的裝置驅動程式。當所需的驅動程式載入完後,會建立一個根裝置,然後將根檔案系統rootfs以只讀的方式掛載。這一步結束後,釋放未使用的記憶體,轉換到真正的根檔案系統上面去,同時執行/sbin/init程式,執行系統的1號程序。此後系統的控制權就全權交給/sbin/init程序了。
l 初始化系統
經過千辛萬苦的跋涉,我們終於接近黎明的曙光了。接下來就是最後一步了:初始化系統。/sbin/init程序是系統其他所有程序的父程序,當它接管了系統的控制權先之後,它首先會去讀取/etc/inittab檔案來執行相應的指令碼進行系統初始化,如設定鍵盤、字型,裝載模組,設定網路等。主要包括以下工作:
1)、執行系統初始化指令碼(/etc/rc.d/rc.sysinit),對系統進行基本的配置,以讀寫方式掛載根檔案系統及其它檔案系統,到此係統算是基本執行起來了,後面需要進行執行級別的確定及相應服務的啟動。rc.sysinit所做的事情(不同的Linux發行版,該檔案可能有些差異)如下:
(1)獲取網路環境與主機型別。首先會讀取網路環境設定檔案”/etc/sysconfig/network”,獲取主機名稱與預設閘道器等網路環境。
(2)測試與載入記憶體裝置/proc及usb裝置/sys。除了/proc外,系統會主動檢測是否有usb裝置,並主動載入usb驅動,嘗試載入usb檔案系統。
(3)決定是否啟動SELinux。
(4)介面裝置的檢測與即插即用(pnp)引數的測試。
(5)使用者自定義模組的載入。使用者可以再”/etc/sysconfig/modules/*.modules”加入自定義的模組,此時會載入到系統中。
(6)載入核心的相關設定。按”/etc/sysctl.conf”這個檔案的設定值配置功能。
(7)設定系統時間(clock)。
(8)設定終端的控制檯的字形。
(9)設定raid及LVM等硬碟功能。
(10)以方式檢視檢驗磁碟檔案系統。
(11)進行磁碟配額quota的轉換。
(12)重新以讀取模式載入系統磁碟。
(13)啟動quota功能。
(14)啟動系統隨機數裝置(產生隨機數功能)。
(15)清楚啟動過程中的臨時檔案。
(16)將啟動資訊載入到”/var/log/dmesg”檔案中。
當/etc/rc.d/rc.sysinit執行完後,系統就可以順利工作了,只是還需要啟動系統所需要的各種服務,這樣主機才可以提供相關的網路和主機功能,因此便會執行下面的指令碼。
2)、執行/etc/rc.d/rc指令碼。該檔案定義了服務啟動的順序是先K後S,而具體的每個執行級別的服務狀態是放在/etc/rc.d/rc*.d(*=0~6)目錄下,所有的檔案均是指向/etc/init.d下相應檔案的符號連結。rc.sysinit通過分析/etc/inittab檔案來確定系統的啟動級別,然後才去執行/etc/rc.d/rc*.d下的檔案。
/etc/init.d-> /etc/rc.d/init.d
/etc/rc ->/etc/rc.d/rc
/etc/rc*.d ->/etc/rc.d/rc*.d
/etc/rc.local-> /etc/rc.d/rc.local
/etc/rc.sysinit-> /etc/rc.d/rc.sysinit
也就是說,/etc目錄下的init.d、rc、rc*.d、rc.local和rc.sysinit均是指向/etc/rc.d目錄下相應檔案和資料夾的符號連結。我們以啟動級別3為例來簡要說明一下。
/etc/rc.d/rc3.d目錄,該目錄下的內容全部都是以 S 或 K 開頭的連結檔案,都連結到”/etc/rc.d/init.d”目錄下的各種shell指令碼。S表示的是啟動時需要start的服務內容,K表示關機時需要關閉的服務內容。/etc/rc.d/rc*.d中的系統服務會在系統後臺啟動,如果要對某個執行級別中的服務進行更具體的定製,通過chkconfig命令來操作,或者通過setup、ntsys、system-config-services來進行定製。如果我們需要自己增加啟動的內容,可以在init.d目錄中增加相關的shell指令碼,然後在rc*.d目錄中建立連結檔案指向該shell指令碼。這些shell指令碼的啟動或結束順序是由S或K字母后面的數字決定,數字越小的指令碼越先執行。例如,/etc/rc.d/rc3.d /S01sysstat就比/etc/rc.d/rc3.d /S99local先執行。
3)、執行使用者自定義載入程式/etc/rc.d/rc.local。其實當執行/etc/rc.d/rc3.d/S99local時,它就是在執行/etc/rc.d/rc.local。S99local是指向rc.local的符號連結。就是一般來說,自定義的程式不需要執行上面所說的繁瑣的建立shell增加連結檔案的步驟,只需要將命令放在rc.local裡面就可以了,這個shell指令碼就是保留給使用者自定義啟動內容的。
4)、完成了系統所有的啟動任務後,linux會啟動終端或X-Window來等待使用者登入。tty1,tty2,tty3…這表示在執行等級1,2,3,4的時候,都會執行”/sbin/mingetty”,而且執行了6個,所以linux會有6個純文字終端,mingetty就是啟動終端的命令。
除了這6個之外還會執行”/etc/X11/prefdm-nodaemon”這個主要啟動X-Window
至此,系統就啟動完畢了。以上分析不到的地方還請各位大蝦不吝指正。
關於Linux的其他分析內容下次再繼續寫。
最後附上一張非常完整的系統啟動流程圖,適合各個水平階段的讀者。參考文獻: