Linux啟動(dòng)過(guò)程全接觸
handong 發(fā)表于 2004-12-8 10:27:00
作者:秋水 本文選自:開(kāi)放系統世界——賽迪網(wǎng) 2002年10月10日
關(guān)于Windows啟動(dòng)過(guò)程介紹的文章可謂多如牛毛,而對于Linux的介紹卻是鳳毛麟角。凡是曾經(jīng)使用過(guò)Linux的用戶(hù)可能都會(huì )注意到,當計算機啟動(dòng)時(shí),屏幕上會(huì )出現很多信息。一般情況下,這些信息我們可以通過(guò)以下的命令看到:
cat /var/log/dmesg | more
這些信息究竟有什么含義?這個(gè)問(wèn)題看起來(lái)似乎很容易回答,因為只要在Linux參考書(shū)里查找一下,就會(huì )找出一個(gè)類(lèi)似于這樣的答案:“這是一些內核啟動(dòng)信息……”。但是“內核啟動(dòng)信息”到底是什么意思呢?
要想對Linux內部工作有所了解,就必須要對Linux內核的體系結構有一個(gè)全面的了解。下面我們就去揭開(kāi)它的秘密。在此,我不想解釋Linux內核的體系結構,只想解釋?zhuān)ɑ蛘哒f(shuō)是試圖去解釋?zhuān)┯嬎銠C系統啟動(dòng)進(jìn)程中一些最基本的概念。這里所說(shuō)的啟動(dòng)過(guò)程是指從按下開(kāi)關(guān)到提示符出現的整個(gè)過(guò)程。
啟動(dòng)指的是什么
在操作系統的詞匯里,啟動(dòng)是指通過(guò)處理器執行一些指令,把操作系統的一部分放入到主存中。在啟動(dòng)過(guò)程中,Linux內部的數據結構會(huì )被初始化,會(huì )被賦給一些初始值,并且某些進(jìn)程會(huì )被創(chuàng )建。因為當計算機電源打開(kāi)時(shí),所有的硬件設備都處于一種不可預知的狀態(tài),內存也處于一種不活動(dòng)的隨機狀態(tài),所以,計算機的啟動(dòng)過(guò)程可以說(shuō)是一個(gè)長(cháng)且復雜的任務(wù)。因此,我們必須知道,之所以叫“啟動(dòng)”主要是因為計算機體系結構的原因。
在此提請讀者注意:
1.對計算機內部的工作和內核的操作有一個(gè)基本的了解,對自己非常有益。
2.這篇文章中提到的所有文件,指的都是Linux內核2.4.2-2版本里的文件。這些文件對于所有的Linux內核來(lái)說(shuō)都是相同的,并且可以在任何一個(gè)Linux系統里找到它們,此處我使用的是Red Hat 7.1。
3.在本文里,討論范圍限于IBM PC體系結構。
BIOS及其功能
當計算機打開(kāi)電源時(shí),內存里包含的是一些隨機的數據,所有的東西都沒(méi)有被初始化,操作系統也沒(méi)有被加載。開(kāi)始整個(gè)啟動(dòng)過(guò)程的是一個(gè)特殊的硬件電路,它觸發(fā)CPU的Reset腳的邏輯值。然后,一些CPU的寄存器比如CS(一個(gè)分段寄存器:代碼段寄存器,它指向含有程序指令的段),eip(在執行指令過(guò)程中,當CPU檢測到一個(gè)意外事故發(fā)生時(shí),它會(huì )做出三種類(lèi)型的判斷:錯誤、陷阱、中止,這取決于eip寄存器的值,它存儲在內核模塊棧里)就會(huì )被給定一個(gè)值。接著(zhù),物理地址為0xfffffff0的代碼將被執行。這個(gè)地址被存儲在一個(gè)只讀存儲器(ROM)里。BIOS(基本輸入/輸出系統)實(shí)際上是一段存儲在ROM里的程序。它包含了一系列可以被某些操作系統調用,用于處理計算機各種硬件設備的中斷驅動(dòng)和低級程序。其中微軟的DOS就是這樣的一種操作系統。
Linux是否使用附于計算機系統的BIOS來(lái)初始化硬件設備?或者說(shuō),是否有其它的東西來(lái)完成同樣的任務(wù)?不過(guò)這個(gè)問(wèn)題沒(méi)有那么簡(jiǎn)單,必須要了解一些知識。我們從80386模式開(kāi)始。Intel微處理器實(shí)現地址翻譯(從邏輯地址->線(xiàn)性地址->物理地址)有兩種不同的途徑,分別稱(chēng)作實(shí)模式和保護模式。實(shí)模式存在主要是為了使得處理器可以和較老的處理相兼容。事實(shí)上,所有的BIOS程序都是在實(shí)模式下運行的。但是,Linux內核是在保護模式下運行,而不是在實(shí)模式下。因此,一旦初始化完成后,Linux就不再使用BIOS,而是完全由自己來(lái)為計算機上的所有硬件提供驅動(dòng)程序(這點(diǎn)和DOS是不一樣的)。
那么什么時(shí)候Linux使用保護模式?為什么BIOS不能使用相同的模式?BIOS使用實(shí)模式是因為其在操作過(guò)程中使用的是實(shí)模式地址,并且在計算機剛打開(kāi)電源時(shí),只有實(shí)模式地址可用。一個(gè)實(shí)模式地址由段地址和偏移地址組成,因此,相應的物理地址就為段地址×(2×8)+偏移。
那么,這是不是意味著(zhù)在整個(gè)啟動(dòng)過(guò)程中,Linux就從來(lái)不使用BIOS了呢?答案是否定的。在啟動(dòng)階段,Linux從硬盤(pán)或者其它外部設備加載內核時(shí),需要使用BIOS。
讓我們來(lái)看一下啟動(dòng)時(shí)BIOS主要做了哪些操作:
1.BIOS要對硬件進(jìn)行一系列徹底的檢測。這個(gè)步驟主要是檢查系統安裝有哪些設備,以及它們工作是否正常。通常把這個(gè)步驟叫做自檢(Power-On Self-Test,POST),這時(shí)會(huì )顯示版本及其它很多相關(guān)的硬件信息。
2.BIOS要對硬件進(jìn)行初始化。這一步非常重要,因為它要保證所有的硬件設備在IRQ(中斷請求)和I/O端口操作時(shí)都沒(méi)有沖突。等這步完成以后,它會(huì )顯示一個(gè)已經(jīng)安裝的PCI設備表。
3.接著(zhù)到了操作系統,BIOS將查找一個(gè)可以引導的操作系統。這取決于BIOS的設置,它可以從軟盤(pán)、硬盤(pán)或者光盤(pán)啟動(dòng)。
4.一旦發(fā)現一個(gè)合法的設備,BIOS就會(huì )把其第一扇區的內容復制到物理地址,即從0x00007c00開(kāi)始的內存中,然后跳至剛加載的地址并執行之。
到此為止,BIOS所要做的工作就全部完成了。
自舉程序及其功用
BIOS調用一個(gè)專(zhuān)門(mén)的程序,這個(gè)程序的任務(wù)就是把操作系統的內核調入內存。這個(gè)程序就叫做自舉程序(Boot Loader)。在我們繼續下面內容之前,先來(lái)看一下啟動(dòng)系統的不同途徑。
1.從軟盤(pán)啟動(dòng)Linux
從軟盤(pán)啟動(dòng)時(shí),存儲在軟盤(pán)第一扇區的指令將被加載并執行。這個(gè)指令然后就會(huì )把其余的內核復制到內存中。
Linux內核可以裝在1.44MB的軟盤(pán)里,不過(guò)為了減少磁盤(pán)占用量,它們都進(jìn)行了壓縮。這個(gè)壓縮過(guò)程是在編譯時(shí)完成的,而解壓縮的過(guò)程則由自舉程序完成。
從軟盤(pán)啟動(dòng)Linux時(shí),自舉程序要做的工作非常簡(jiǎn)單。它是一個(gè)位于/usr/src/linux-2.4.2/arch/i386/boot/bootsect.S的匯編語(yǔ)言文件。當我們編譯Linux內核源代碼,或者獲取一個(gè)新的內核時(shí),這個(gè)可執行的匯編代碼就會(huì )被放在內核程序的前端。由此可見(jiàn),要制作一個(gè)可啟動(dòng)的Linux軟盤(pán)其實(shí)很簡(jiǎn)單。我們只要從磁盤(pán)的第一個(gè)扇區拷貝Linux內核,就可以創(chuàng )建一個(gè)可啟動(dòng)軟盤(pán)。當BIOS加載軟盤(pán)的第一個(gè)扇區時(shí),它實(shí)際上拷貝的是自舉程序。自舉程序由BIOS調用(跳到物理地址為0x00007c00的位置),然后執行以下的操作:
(1)把自已從地址0x00007c00移動(dòng)到0x00090000;
(2)使用地址0x00003ff4,創(chuàng )建“實(shí)模式”棧;
(3)設置磁盤(pán)參數表,這里使用的是BIOS提供的軟盤(pán)驅動(dòng)程序;
(4)通過(guò)調用BIOS程序顯示“Loading”信息;
(5)自舉程序調用BIOS程序來(lái)加載軟盤(pán)上內核的setup()函數,并把它放在起始地址為0x00090200的內存中;
(6)接下來(lái)自舉程序調用一個(gè)BIOS程序,這個(gè)程序從軟盤(pán)加載剩余的內核程序,并將其放入起始地址為0x00010000(所謂的低地址)或者0x00100000(所謂的高地址);
(7)然后,跳轉到setup()函數。
2.從硬盤(pán)啟動(dòng)Linux
當系統從硬盤(pán)啟動(dòng)時(shí),啟動(dòng)過(guò)程又有所不同。硬盤(pán)的第一個(gè)扇區叫做MBR(Master Boot Record),其上存儲著(zhù)分區表和一個(gè)小程序。這個(gè)程序加載存儲由操作系統的第一扇區來(lái)開(kāi)始啟動(dòng)。Linux是一個(gè)高度靈活且非常優(yōu)秀的軟件,所以在MBR里,它使用一個(gè)叫做LILO的程序來(lái)代替上述的那個(gè)程序。LILO允許用戶(hù)選擇所要啟動(dòng)的操作系統。
一般來(lái)說(shuō),Linux是從硬盤(pán)啟動(dòng)的。這就需要不同的自舉程序。在Intel系統里,用得最多的自舉程序就是LILO。對于其它的體系結構,還存在著(zhù)別的自舉程序。LILO可以安裝在MBR上(請注意:在安裝Red Hat Linux時(shí),有一個(gè)步驟會(huì )讓用戶(hù)選擇把LILO安裝到MBR或者引導扇區)或一個(gè)活動(dòng)分區的引導扇區上。
由于LILO太大,MBR無(wú)法容納,所以它被分成兩部分。MBR(或者磁盤(pán)分區的引導扇區)包含有一個(gè)小的自舉程序,它被BIOS載入到起始地址為0x00007c00的內存中。然后,這個(gè)小程序再把自己移到0x0009a000地址處,接著(zhù)設置實(shí)模式棧,最后加載第二部分的LILO自舉程序(請注意:實(shí)模式棧地址范圍是0x0009b000 到 0x0009a200)。
第二部分的LILO會(huì )從磁盤(pán)讀取所有可用的操作系統,并且給用戶(hù)列出,以選擇所要啟動(dòng)的系統。一旦用戶(hù)選擇完成,自舉程序就會(huì )加載相應的扇區內容到內存中并且執行之。
自舉程序被BIOS調用時(shí)(跳到物理地址為0x00007c00處),要執行以下操作:
(1)把自已從地址0x00007c00移動(dòng)到0x00090000;
(2)使用地址0x00003ff4,創(chuàng )建“實(shí)模式”棧;
(3)設置磁盤(pán)參數表。這里使用的是BIOS提供的軟盤(pán)驅動(dòng)程序;
(4)通過(guò)調用BIOS程序顯示“Loading Linux”信息;
(5)自舉程序調用BIOS程序來(lái)加載軟盤(pán)上內核的setup()函數,并把它放在起始地址為0x00090200的內存中;
(6)接下來(lái)自舉程序調用一個(gè)BIOS程序,這個(gè)程序從軟盤(pán)加載剩余的內核程序,并將其放入起始地址為0x00010000或者0x00100000;
(7)然后,跳轉到setup()函數。
Setup()函數的功用
現在我們就可以深入研究一下自舉過(guò)程中不可缺少的匯編語(yǔ)言函數了。
Setup()函數可以在/usr/src/linux-2.4.2/arch/i386/boot/setup.S文件中找到。
Setup()函數代碼是在完整的內核自舉程序加載以后,才會(huì )跳到相應的函數代碼處。在內核文件中,其偏移地址是0x200。這使得自舉程序很容易找到這段代碼,并將其拷貝到起始物理地址為0x00090200的內存中。
這個(gè)Setup()文函數到底是做什么用的?在計算機時(shí)里,內核要正確地操作所有硬件就必需首先要檢測到它們,并且以一種有序的方式進(jìn)行初始化。Setup()函數初始化所有的硬件設備,從而為內核操作它創(chuàng )造了一個(gè)環(huán)境。
但是,前面我們不是已經(jīng)提到過(guò)BIOS會(huì )檢測所有的硬件嗎?雖然BIOS初始化了所有的硬件,但是Linux內核并不放心,它還要以自己的方式對所有的硬件進(jìn)行初始化。Linux內核之所以要設計成這樣,是為了增強可移植性和穩定性。這也是Linux內核要優(yōu)于很多目前可用的Unix和類(lèi)Unix內核的原因之一,并且也使得它在很多方面表現的非常出眾。
Setup()函數主要完成以下任務(wù):
(1)首先是檢測系統可用內存的總量,它是通過(guò)BIOS程序來(lái)完成檢測的;
(2)設置鍵盤(pán)重復延遲時(shí)間和重復速度;
(3)檢測視頻卡;
(4)重新初始化硬盤(pán)控制器和硬盤(pán)參數;
(5)檢測一個(gè)MCA;
(6)檢測一個(gè)PS/2定點(diǎn)設備(鼠標總線(xiàn));
(7)檢測高級電源管理器(APM)BIOS支持;
(8)檢測內核在內存中的位置,如果在低地址0x00010000,就將其移到高地址0x00001000,如在高地址則不做任何移動(dòng);
(9)設置設備中斷描述表(IDT)和全局描述表(GDT);
(10)如已經(jīng)有了浮點(diǎn)單位(FPU),則重置之;
(11)重新調用程序中斷控制器;
(12)通過(guò)設置cr0狀態(tài)寄存器的PE位,把CPU從“實(shí)模式”切換到“保護模式”;
(13)跳轉到stratup_32( )匯編語(yǔ)言函數。
第一個(gè)stratup_32( )函數做什么
在啟動(dòng)過(guò)程中要用到兩個(gè)stratup_32( )函數,雖然它們都是匯編語(yǔ)言函數,但是卻是兩個(gè)完全不同的函數。我們這里所說(shuō)的函數包含在/usr/src/linux-2.4.2/arch/i386/boot/compressed/head.S文件里。
Setup()文件執行后,這個(gè)函數就被加載到物理地址為0x00100000或者物理地址為0x00001000的內存中(取決于內核是載入高或者低內存)。
當執行這個(gè)函數時(shí),會(huì )執行以下的操作:
(1)初始化段寄存器和一個(gè)臨時(shí)棧。
(2)內核中沒(méi)有初始化的數據都用0填充。它是通過(guò)symbols _edata和 _end來(lái)識別的。
(3)執行decompress_kernel( )函數。這個(gè)函數用于對Linux內核解壓縮。這個(gè)時(shí)候,屏幕上將顯示“Uncompressing Linux……”信息。解壓縮完成后,就會(huì )顯示“OK, booting the kernel”信息?,F在有一個(gè)問(wèn)題,就是解完壓縮的內核被放置在什么位置?答案是如果Linux內核被加載低地址,那么解壓縮的內核將被置于物理地址為0x00100000的地方。如果在高地址,則內核會(huì )被先解壓到一個(gè)臨時(shí)緩沖區中,待完成后再將其加載到物理地址為0x00100000的地方。
(4)最后,跳轉到物理地址為0x00100000的地方執行。
到此為止,代碼執行操作就由另外一個(gè)startup_32( )函數來(lái)接管。也就是說(shuō),第二個(gè)startup_32( )函數接管了啟動(dòng)過(guò)程。
第二個(gè)startup_32( )函數完成的功能
解壓縮Linux內核的工作由另外一個(gè)startup_32( )函數來(lái)完成。該函數位于/usr/src/linux-2.4.2/arch/i386/kernel/head.S文件中。
這時(shí)你可能會(huì )說(shuō)兩個(gè)不同的函數用同一個(gè)名字不會(huì )出錯嗎?答案是不會(huì )的。因為兩個(gè)函數都是到自己初始地址去執行,并且都有自己的執行環(huán)境,所以不會(huì )出錯。
下面我們來(lái)看一下第二個(gè)startup_32( )函數的功能。當執行這個(gè)函數時(shí),實(shí)際上是為第一個(gè)Linux進(jìn)程(process 0)設置環(huán)境。這個(gè)函數將執行下面的操作:
(1)段寄存器將以最后的值進(jìn)行初始化;
(2)為process 0設置內核模式棧;
(3)調用并且執行setup_idt( )函數,該函數將把所有的IDT填充空值;
(4)把從BIOS中獲得的參數放在第一頁(yè)的框架中;
(5)識別處理器的模式;
(6)使用GDT和IDT表加載gdtr和idtr寄存器;
(7)最后跳到start_kernel( )函數。
start_kernel( )函數功能
start_kernel( )函數完成Linux內核的初始化工作。這個(gè)函數執行后,所有的基本內核組件都將被初始化。這也是整個(gè)啟動(dòng)過(guò)程的最后一步。
該函數將完成以下的功能:
(1)執行paging_init( )函數初始化頁(yè)表(Page Tables);
(2)執行mem_init( )函數初始化頁(yè)描述符(Page Descriptors);
(3)執行trap_init( ) 和 init_IRQ( )函數,最后一次對IDT進(jìn)行初始化;
(4)執行kmem_cache_init( )和kmem_cache_sizes_init ( )函數,對Slab Allocator進(jìn)行初始化;
(5)執行time_init( )函數,初始化系統日期和時(shí)間;
(6)內核的線(xiàn)程process 1是通過(guò)調用kernel_thread( )來(lái)完成的。接著(zhù)就建立其它的內核線(xiàn)程并且執行/sbin/init程序。
到此屏幕上就會(huì )顯示“Linux version 2.4.2 ……”信息。此外,還會(huì )顯示很多其它信息。最后,就會(huì )出現用戶(hù)的登錄提示符。這是在告訴用戶(hù)Linux內核已經(jīng)加載完成,用戶(hù)已經(jīng)可以使用。