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