|
深入理解Linux啟動(dòng)過程
本文詳細(xì)分析了Linux桌面操作系統(tǒng)的啟動(dòng)過程,涉及到BIOS系統(tǒng)、LILO 和GRUB引導(dǎo)裝載程序,以及bootsect、setup、vmlinux等映像文件,并結(jié)合引導(dǎo)、啟動(dòng)原理和具體的代碼實(shí)現(xiàn)機(jī)制由淺入深地進(jìn)行了分析。
初學(xué)者剛接觸Linux桌面系統(tǒng)會(huì)感覺系統(tǒng)啟動(dòng)速度較慢,那么,為什么它的啟動(dòng)速度慢呢?本文就桌面系統(tǒng)的引導(dǎo)和啟動(dòng)過程展開分析,以期對(duì)初學(xué)者熟悉Linux有所幫助。
一、Linux系統(tǒng)的引導(dǎo)過程
簡(jiǎn)單地說,系統(tǒng)的引導(dǎo)和啟動(dòng)過程就是計(jì)算機(jī)加電以后所要發(fā)生的事情, 比如,加電自檢、引導(dǎo)程序的拷貝和執(zhí)行、內(nèi)核的拷貝和執(zhí)行及用戶程序的執(zhí)行等。這個(gè)過程就是常說的bootstrap,我們把這些歸納為5個(gè)過程, 下面來逐一分析。
1.BIOS執(zhí)行階段
現(xiàn)代計(jì)算機(jī)系統(tǒng)的存儲(chǔ)機(jī)制是“揮發(fā)”性的,一旦關(guān)機(jī)斷電, 存儲(chǔ)在內(nèi)存中的信息。連同操作系統(tǒng)本身的映射就丟失了。所以,必須把操作系統(tǒng)(內(nèi)核) 的映像存儲(chǔ)在某些不“揮發(fā)” 的介質(zhì)中,使得開機(jī)加電時(shí)由一個(gè)不“揮發(fā)”介質(zhì)加載操作系統(tǒng),并轉(zhuǎn)入運(yùn)行的過程。這就是引導(dǎo),也稱自舉。這些不“揮發(fā)” 介質(zhì)通常是指硬盤或軟盤, 也可以是EPROM 或F1ash存儲(chǔ)器,還可以是網(wǎng)絡(luò)中別的節(jié)點(diǎn)。要想在開機(jī)時(shí)從不“揮發(fā)” 介質(zhì)裝入操作系統(tǒng)的映像,系統(tǒng)就要CPU在開機(jī)時(shí)能執(zhí)行一段程序,這段程序本身必須存儲(chǔ)在作為系統(tǒng)內(nèi)存一部分的EPROM 或Flash等存儲(chǔ)器中, 而且它們知道怎樣才能從不“揮發(fā)” 介質(zhì)裝入操作系統(tǒng)的映像。事實(shí)上,各種CPU 被設(shè)計(jì)成一個(gè)加電后就從某個(gè)特殊的地址開始執(zhí)行指令,所以這些不揮發(fā)存儲(chǔ)器就被安置在這個(gè)位置上。比如在i386CPU系統(tǒng)中,計(jì)算機(jī)在加電的那一刻,RAM 芯片中所包含的是隨機(jī)數(shù)據(jù),還沒有操作系統(tǒng),在此刻有一個(gè)特殊硬件電路在加電時(shí)會(huì)在C P U 的一個(gè)引腳上產(chǎn)生一個(gè)RESET邏輯值,硬件電路設(shè)置RESET邏輯值以后,代碼寄存器CS的內(nèi)容為0xffff,而指令寄存器的內(nèi)容為0。也就是說,CPU要從線性地址0xffff0開始處取第一條指令。硬件電路再把這個(gè)物理地址映射到RAM 芯片中,BIOS就存放在這里,這時(shí)候處理器就開始執(zhí)行BIOS代碼了。我們都知道BIOS中包含了幾個(gè)中斷驅(qū)動(dòng)的低級(jí)程序,可以使用它們來初始化一些硬件設(shè)備,但它們是在實(shí)模式下工作的。其中實(shí)模式地址是由一個(gè)seg段和一個(gè)off偏移量組成的,相應(yīng)的物理地址可以使用“seg*16+off” 來計(jì)算。
接下來BIOS要做的就是執(zhí)行一系列的測(cè)試,看看到底系統(tǒng)中有什么設(shè)備,以及這些設(shè)備是否正常工作。在執(zhí)行這個(gè)過程時(shí),會(huì)顯示一些如BIOS系統(tǒng)的版本號(hào)等信息。當(dāng)檢測(cè)到可用的設(shè)備后就進(jìn)行一些初始化工作,比如初始化PCI設(shè)備以避免I RQ線與I/O端口的沖突,最后顯示系統(tǒng)中安裝的所有PCI設(shè)備的一個(gè)列表。
在早期的計(jì)算機(jī)系統(tǒng)中, 類似于BIOS功能的程序非常小,并且不同時(shí)期這段程序的設(shè)計(jì)也不相同。在PC發(fā)展早期, 由于當(dāng)時(shí)存儲(chǔ)芯片大小的限制,使得該段程序的目的和功能都很單一 再說,如此小的一段程序很難依靠自身的力量把龐大的操作系統(tǒng)的映像從磁盤里讀進(jìn)來。于足,人們又提出了引導(dǎo)扇區(qū)的概念,使得存儲(chǔ)在引導(dǎo)扇區(qū)中的程序來協(xié)助BIOS完成操作系統(tǒng)的引導(dǎo)工作。但是,引導(dǎo)扇區(qū)的大小也不過5l2個(gè)字節(jié), 能夠容納的信息和代碼也很有限,所以說,操作系統(tǒng)的引導(dǎo)代碼是一個(gè)循序漸進(jìn)的過程,它分布在不同的角落。當(dāng)BlOS根據(jù)設(shè)置將相應(yīng)的啟動(dòng)設(shè)備的第一個(gè)扇區(qū)的內(nèi)容拷貝到RAM 中時(shí), 這些內(nèi)容被放在物理地址0x0O007c00開始的地方。此后,系統(tǒng)就開始跳到這個(gè)地址,并開始執(zhí)行相應(yīng)的代碼。
2.Boot Loader階段
如此小的引導(dǎo)記錄要完成這么大的任務(wù),壓力是不小的,所以引導(dǎo)扇區(qū)的程序及輔助程序必須很簡(jiǎn)練,它們都采用匯編語(yǔ)言編寫,這些源代碼都存放在arch/ 下具體CPU名下的boot目錄中,如bootsect.S、setup.S和video.S。其中bootsect.S是Linux引導(dǎo)扇區(qū)的源代碼。這樣,經(jīng)過編譯、匯編和連接以后,形成了3個(gè)組成部分,即引導(dǎo)扇區(qū)的映像bootsect、輔助程序setup及內(nèi)核映像本身(通常是vmlinux,有時(shí)也用uImage)。嚴(yán)格地說,bootsect和setup并不是內(nèi)核的一部分。
引導(dǎo)裝載程序就是由BIOS來把操作系統(tǒng)的內(nèi)核映像裝入到RAM 中所調(diào)用的一個(gè)程序。這里我們選擇用硬盤啟動(dòng)來說明引導(dǎo)裝載程序的執(zhí)行過程。說起硬盤,大家都知道它是由許許多多的扇區(qū)和柱面組成,其中把第一個(gè)扇區(qū)稱為主引導(dǎo)記錄(Master Boot Record,MBR),在該扇區(qū)中包含了分區(qū)信息和一個(gè)小程序,這個(gè)小程序用來裝載被啟動(dòng)的操作系統(tǒng)所在分區(qū)的第一個(gè)扇區(qū)。說到這里我們就要注意,這一段Windows系統(tǒng)和Linux系統(tǒng)是有區(qū)別的:Windows系統(tǒng)使用分區(qū)表中所包含的一個(gè)active標(biāo)志來標(biāo)識(shí)這個(gè)分區(qū),當(dāng)然這個(gè)分區(qū)也可以使用FDISK之類的程序進(jìn)行設(shè)置,但有一個(gè)條件就是只有那些內(nèi)核映像存放在活動(dòng)分區(qū)的操作系統(tǒng)才可以啟動(dòng)。Linux系統(tǒng)的處理方法要更靈活些,它使用GRUB或是LILO程序把這個(gè)包含在MBR 中的不完善引導(dǎo)裝載程序給替掉。裝入程序在啟動(dòng)過程中被執(zhí)行時(shí),用戶可以選擇裝入哪個(gè)操作系統(tǒng)。但LILO和GRUB的工作原理又不盡相同,關(guān)于它們的詳細(xì)介紹可以查閱相關(guān)資料。LILO 引導(dǎo)裝入程序被分為兩部分,MBR 或分區(qū)引導(dǎo)扇區(qū)包括一個(gè)小的引導(dǎo)裝入程序,由BIOS把這個(gè)小程序裝入從地址0xO0007c00開始的RAM 中,這個(gè)小程序又把自己移到地址0x0009a000, 然后建立實(shí)模式棧。接著把LILO 的第二部分裝入到從地址0x0009b000開始的RAM 中, 第二部分又讀取可用操作系統(tǒng)的映射表,并給用戶一個(gè)提示符號(hào)。這個(gè)時(shí)候用戶可以從中選擇一個(gè)操作系統(tǒng)進(jìn)行啟動(dòng),引導(dǎo)裝入程序就可以把相應(yīng)分區(qū)的引導(dǎo)扇區(qū)拷貝到RAM 中并執(zhí)行,或者是直接把內(nèi)核映像拷貝到RAM 中。在拷貝內(nèi)核的過程中,首先是把內(nèi)核映像所集成的引導(dǎo)裝入程序拷貝到地址0xO009000,然后把setup()代碼拷貝到地址0x00090200,最后把內(nèi)核映像的其余部分拷貝到地址0x00010000或0x00100000, 最終系統(tǒng)執(zhí)行跳到setup()代碼上。
3.Setup函數(shù)執(zhí)行階段
Setup()是匯編語(yǔ)言函數(shù)代碼,它在內(nèi)核的編譯鏈接過程中被放到內(nèi)核的引導(dǎo)裝入程序之后,也就是內(nèi)核映像文件的偏移量0x200地址處,實(shí)際物理地址0x00090200開始的RAM 中。因?yàn)閮?nèi)核不依賴于BIOS, 雖然BIOS已經(jīng)初始化了大部分硬件設(shè)備,但Linux系統(tǒng)還要以自己的方式重新初始化設(shè)備,以增加可移植性和健壯性。還要注意的是,內(nèi)核是工作在保護(hù)模式下的。總的來說,setup()函數(shù)的作用就是初始化計(jì)算機(jī)中的硬件設(shè)備,并為內(nèi)核程序的執(zhí)行建立環(huán)境。比如,檢查系統(tǒng)中可用的RAM 數(shù)量、設(shè)置鍵盤重復(fù)延時(shí)速率、顯卡等其他設(shè)備的檢查,以及初始化和切換實(shí)模式到保護(hù)模式等。最后,系統(tǒng)執(zhí)行跳到startup_ 32匯編函數(shù)上。
二、Linux系統(tǒng)的啟動(dòng)過程
當(dāng)內(nèi)核映像被裝載到RAM芯片后,就開始執(zhí)行內(nèi)核的代碼,這意味著引導(dǎo)完成,開始進(jìn)入Linux系統(tǒng)的啟動(dòng)過程。
1. Startup_32函數(shù)的執(zhí)行階段
在系統(tǒng)的啟動(dòng)過程中有兩個(gè)startup_320()函數(shù),即位于arch/i386/boot/compressed/head.S文件中實(shí)現(xiàn)的。就是在setup()函數(shù)結(jié)束以后,該函數(shù)就被移動(dòng)到物理地址0X00100000或0x00001000處,這取決于內(nèi)核映像是被裝到RAM 的高位還是底位。因?yàn)閮?nèi)核映像文件在編譯連接時(shí)所產(chǎn)生的大小不同, 如zImage和bzImage大小相差很大,在裝載解壓時(shí)所使用的緩沖區(qū)也不同,所以他們所處的物理地址是不同的。不過解壓后的映像最終都處在物理地址0x00100000開始的位置。然后跳轉(zhuǎn)到這個(gè)地址處執(zhí)行解壓后的映像中的另一個(gè)startup_32()函數(shù),這個(gè)函數(shù)為第一個(gè)Linux進(jìn)程(進(jìn)程0)建立執(zhí)行環(huán)境,該函數(shù)初始化段寄存器、為進(jìn)程0建立內(nèi)核態(tài)堆棧等一系列活動(dòng)。最后識(shí)別處理器模式,并跳轉(zhuǎn)到start_kernel()函數(shù)。將Linux內(nèi)核的映像裝入內(nèi)存,并且setup()函數(shù)做了一些必要的準(zhǔn)備,就該startup_32函數(shù)開始干活了。CPU通過一條長(zhǎng)程轉(zhuǎn)移指令轉(zhuǎn)到映像代碼段開頭的入口startup_32處,對(duì)于SMP結(jié)構(gòu)的系統(tǒng)來說,這個(gè)時(shí)候運(yùn)行的只是其中的一個(gè)處理器,就是所謂的主CPU。其他的次CPU 處于停機(jī)狀態(tài), 等待主CPU 的啟動(dòng)。次CPU在受到啟動(dòng)進(jìn)入內(nèi)核時(shí),同樣也要從startup_32開始執(zhí)行,所以從startup_32開始的代碼是公共的。但有些操作僅由主CPU來執(zhí)行,另一些操作由次CPU執(zhí)行, 這并不意味著主CPU 和次CPU 并發(fā)地執(zhí)行這段程序。實(shí)際上,主CPU 是開路先鋒,首先執(zhí)行這段程序,完成以后逐個(gè)啟動(dòng)次CPU執(zhí)行,并且等待其完成。所以,在同一時(shí)間系統(tǒng)中最多只有一個(gè)處理器在執(zhí)行這段程序。不管是主CPU還是次CPU,進(jìn)入startup_32時(shí)都運(yùn)行在保護(hù)模式下的段式尋址方式,等到第二個(gè)startup_32函數(shù)執(zhí)行到最后時(shí), 就開始執(zhí)行start_kernel函數(shù)。
2. Start_kernel函數(shù)執(zhí)行階段
到了這個(gè)階段才是真正的內(nèi)核初始化階段,幾乎內(nèi)核每個(gè)部分的初始化工作都是由這個(gè)函數(shù)來完成,如頁(yè)表的初始化、系統(tǒng)日期和時(shí)間的初始化等。從某種意義上說,函數(shù)Start_kernel就好像一般可執(zhí)行程序中的主函數(shù)main(),系統(tǒng)在進(jìn)入這個(gè)函數(shù)之前已經(jīng)進(jìn)行了一些最底限度的初始化,為這個(gè)函數(shù)的執(zhí)行建立起了一個(gè)環(huán)境,創(chuàng)造了必要的條件。當(dāng)然,這個(gè)函數(shù)還要繼續(xù)進(jìn)行內(nèi)核的初始化,甚至可以說內(nèi)核的初始化在這里才真正開始,但它是較高層次的初始化。這個(gè)函數(shù)的代碼在init/main.C中,從現(xiàn)在開始初始化流程不與CPU 類型和系統(tǒng)啟動(dòng);方式相關(guān)了。此時(shí)系統(tǒng)運(yùn)行在CPU的特權(quán)級(jí),也就是我們常說的內(nèi)
核模式下。start_ kernel函數(shù)主要完成一些數(shù)據(jù)結(jié)構(gòu)的初始化,主要包括
如下:
printk(linux_banner) 輸出
Linux版本信息;
Setup_arch()(arch/i386/kernel/traps.C)執(zhí)行與體系結(jié)構(gòu)相關(guān)的設(shè)置,如內(nèi)存分析分配內(nèi)
核頁(yè)表, 處理啟動(dòng)命令行等;
Trap_init() 設(shè)置各種人口地址,如異常事件處理程序入口, 系統(tǒng)調(diào)用人口,
IniLIRQ() 初始化IRQ 中斷處理機(jī)制;
Sched_init() 設(shè)置并啟動(dòng)第一個(gè)進(jìn)程ini_task0 l
Softirq_init() 對(duì)軟中斷子系統(tǒng)進(jìn)行初始化;
Time_initO 讀取實(shí)時(shí)時(shí)間,重新設(shè)置時(shí)鐘中斷irq0的中斷服務(wù)程序入口等;
Console__init() 初始化控制臺(tái)和顯示器;
Init_modules() 初始化
kernel__m odule l
Kmem_cache_init0 對(duì)內(nèi)存的slab分配機(jī)制初始化{
Mem_init() 虛擬內(nèi)存計(jì)算以及初始化;
Kmem_cache_size_jnit() 初始化slab分配器中的內(nèi)部cashe和全局cashel
Fork_init() 定義了系統(tǒng)的最大進(jìn)程數(shù)目。此外,還有一些對(duì)其他支持的初始化。
隨后,進(jìn)入reset—init0函數(shù)調(diào)用kernel__thread()函數(shù)為進(jìn)程1創(chuàng)建init內(nèi)核線程,這個(gè)內(nèi)核線程又會(huì)創(chuàng)建其他的內(nèi)核線程程序,并執(zhí)行/sbin/init程序。此后start_kernel進(jìn)入一個(gè)空閑等待循環(huán)(cpu_idle()), 使用系統(tǒng)初始化后CPU 的空閑時(shí)間片,init內(nèi)核線程首先要鎖定內(nèi)核,然后調(diào)用do_basic_setup()來初始化外部設(shè)備及加載驅(qū)動(dòng)程序。在do_basic_setup()函數(shù)調(diào)用完之后,init()函數(shù)會(huì)釋放初始化函數(shù)所用的內(nèi)存,并且打開/dev/console設(shè)備重新定向控制臺(tái),使用系統(tǒng)調(diào)用execve來執(zhí)行用戶態(tài)程序/sbin/init。
到目前為止,Linux內(nèi)核的初始化工作完成,此時(shí)系統(tǒng)中已經(jīng)存在5個(gè)運(yùn)行實(shí)體:init線程、kflushd核心線程、kupdate核心線程、kswapd核心線程和keventd核心線程。本身所在的執(zhí)行體其實(shí)就是一個(gè)線程,不過是由手工創(chuàng)建的。它在創(chuàng)建了init0線程以后就進(jìn)入cpu_idle循環(huán), 不會(huì)在進(jìn)程列表中出現(xiàn)。如果使用pstree命令,則不能列出該線程。
最后,init程序會(huì)根據(jù)inittab文件中的設(shè)置信息啟動(dòng)相應(yīng)的用戶程序。當(dāng)init得到控制并啟動(dòng)mingetty顯示登錄界面及提示后,系統(tǒng)啟動(dòng)完成。
三、小結(jié)
從加電自檢開始, 引導(dǎo)過程要經(jīng)歷數(shù)十個(gè)回合來拷貝執(zhí)行,使用不同的引導(dǎo)裝載程序所使角的流程也不同。當(dāng)把內(nèi)核映像拷貝到RAM中展開后, 內(nèi)核開始掌管主權(quán),開始了自己的 “事業(yè)”。內(nèi)核線程init()的任務(wù)仍然還是初始化,當(dāng)然是進(jìn)一步的、更高層次上的初始化。
事實(shí)上,從引導(dǎo)結(jié)束、CPU轉(zhuǎn)入內(nèi)核映像開始,一共有三個(gè)階段的初始化:第一階段是從進(jìn)入startup_32()開始, 到進(jìn)入start_kernel()或start_ secondary()。這個(gè)階段主要是對(duì)CPU 自身的初始化,主CPU和次CPU 都要經(jīng)歷這種初始化,但是主CPU要多一些貢獻(xiàn)。第二階段是從進(jìn)入start_kernel()開始,到進(jìn)入cpu_idle()。這個(gè)階段主要是對(duì)系統(tǒng)的寶貴資源的初始化, 僅由主CPU進(jìn)行。第三階段是init()的執(zhí)行,這是對(duì)系統(tǒng)接近用戶層的初始化, 這個(gè)時(shí)候表面上看已經(jīng)沒:有主CPU和次CPU之分,但誰(shuí)執(zhí)行init()取決于競(jìng)爭(zhēng)調(diào)度的結(jié)果。事實(shí)上,由于主CPU預(yù)先留了一手, 實(shí)際上還是由它來執(zhí)行。
|
|
|