?? 4.txt
字號:
第4章 系統(tǒng)初始化當(dāng)你想要運(yùn)行程序時,你需要把程序的文件名敲入shell—或者更為流行的,在如GNOME或者KDE等之類桌面環(huán)境中點(diǎn)擊相應(yīng)的圖標(biāo),這樣就能將其裝載進(jìn)內(nèi)核并運(yùn)行。但是,首先必須有其他的軟件來裝載并運(yùn)行內(nèi)核;這通常是諸如LOADLIN或者LILO之類的內(nèi)核引導(dǎo)程序。更進(jìn)一步說,我們還需要其他的軟件來裝載運(yùn)行內(nèi)核引導(dǎo)程序—稱之“內(nèi)核引導(dǎo)程序的引導(dǎo)程序”,而且看起來似乎運(yùn)行內(nèi)核引導(dǎo)程序的引導(dǎo)程序也需要內(nèi)核引導(dǎo)程序的引導(dǎo)程序的引導(dǎo)程序,等等,這個過程是無限的。這個無限循環(huán)的過程必然最終在某個地方終止,這就是硬件。因此,在最低的層次上,啟動系統(tǒng)的第一步是從硬件中獲得幫助。該硬件總是運(yùn)行一些短小的內(nèi)置程序—軟件,但是這些軟件是被固化在只讀存儲器中,存儲在已知地址中。因此,在這種情況下就不需要軟件引導(dǎo)程序了—它能夠運(yùn)行更大更復(fù)雜的程序,直到內(nèi)核自身裝載成功為止。按照這種方式,系統(tǒng)自己的引導(dǎo)過程(bootstrap)會引發(fā)系統(tǒng)的啟動,當(dāng)然這只是術(shù)語“系統(tǒng)引導(dǎo)(booting)”的一個比喻。雖然不同體系結(jié)構(gòu)的引導(dǎo)過程的具體細(xì)節(jié)差異很大,但是它們的原則都基本相同。前面的工作都完成以后,內(nèi)核就已經(jīng)成功裝載了。隨后內(nèi)核可以初始化自身以及系統(tǒng)的其他部分。本章首先將簡單介紹基于x86 PC機(jī)的典型自啟動方式,接著回顧一下每一步工作在什么時機(jī)發(fā)生,最后我們還要介紹的是內(nèi)核的相應(yīng)部分。4.1 引導(dǎo)PC機(jī)本節(jié)簡要介紹x86 PC是如何引導(dǎo)的。本節(jié)的目的不是讓你精通PC是怎樣引導(dǎo)的(這超出了本書的范圍),而是向你展示特定體系結(jié)構(gòu)一般的引導(dǎo)方式,為下文中的內(nèi)核初始化進(jìn)行鋪墊。首先,機(jī)器中的每個CPU都要自行初始化,接著可能要用幾分之一秒的時間來執(zhí)行自測試。在多處理器的系統(tǒng)中,這個過程會更復(fù)雜些—但是實(shí)際上也并不多。在雙處理器的Pentium系統(tǒng)中,一個CPU總是作為主CPU存在,另外一個CPU則是輔CPU。主CPU執(zhí)行啟動過程中的剩余工作,隨后內(nèi)核才會激活輔CPU。在多處理器的Pentium Pro系統(tǒng)中,CPU必須根據(jù)Intel定義的算法“搶奪標(biāo)志”來動態(tài)決定由哪個CPU啟動系統(tǒng)。取得標(biāo)志的CPU啟動系統(tǒng),隨后內(nèi)核激活其他的CPU。無論是哪種情況,啟動程序的剩余部分只與一個CPU有關(guān)。這樣,在隨后的一段時間內(nèi),我們可以認(rèn)為該系統(tǒng)中只有一個CPU是可用的,而不考慮其他的CPU,或者說這些CPU被暫時隱藏了。另一方面,內(nèi)核還需要明確地激活所有其他的CPU—這一點(diǎn)你可以在本章后續(xù)部分看到。接下來,CPU從0xfffffff0地址單元中取得指令并執(zhí)行,這個地址非常接近于32位CPU的最后可用的地址。因?yàn)榇蠖鄶?shù)PC都沒有4GB的RAM,所以通常在這個地址上并沒有實(shí)際內(nèi)存。內(nèi)存硬件可以虛擬使用它。對那些確實(shí)有4GB內(nèi)存的機(jī)器來說,它們也只是僅僅損失了供BIOS使用的頂端地址空間末尾的少量內(nèi)存(實(shí)際上BIOS在這里只保留了64K的空間—這種損失在4GB的機(jī)器中是可以忽略的)。該地址單元中存儲的指令是一條跳轉(zhuǎn)指令,這條指令跳轉(zhuǎn)到基本輸入輸出系統(tǒng)(BIOS)代碼的首部。BIOS內(nèi)置在主板中,它主要負(fù)責(zé)控制系統(tǒng)的啟動。請注意CPU實(shí)際上并不真正關(guān)心BIOS是否存在,這樣就使得在諸如用戶定制的嵌入系統(tǒng)之類的非PC體系結(jié)構(gòu)的計算機(jī)中使用Intel的CPU成為可能。CPU執(zhí)行在目標(biāo)地址中發(fā)現(xiàn)的任何指令,在這里使用跳轉(zhuǎn)指令轉(zhuǎn)移到BIOS只是PC體系結(jié)構(gòu)的一部分(實(shí)際上,跳轉(zhuǎn)指令自己是BIOS的一部分,但是這不是考慮這個問題的最方便的方法)。BIOS使用內(nèi)置的規(guī)則來選擇啟動設(shè)備。通常情況下,這些規(guī)則是可以改變的,方法是在啟動過程開始時按下一個鍵(例如,在我的系統(tǒng)中是Del鍵)并通過一些菜單選項(xiàng)瀏覽選擇。但是,通常的過程是BIOS首先試圖從軟盤啟動,如果失敗了,就再試圖從主硬盤上啟動。如果又失敗了,就再試圖從CD-ROM上啟動。為了使問題更具體,這里討論的情況假定是最普通的,也就是啟動設(shè)備是硬盤。從這種啟動設(shè)備上啟動,BIOS讀取第一個扇區(qū)的信息—首512個字節(jié),稱之為主引導(dǎo)記錄(MBR)。接下來發(fā)生的內(nèi)容有賴于Linux是怎樣在系統(tǒng)上安裝的。為使討論形象具體,我們假定LILO是內(nèi)核的載入程序。在典型的設(shè)置中,BIOS檢測MBR中的關(guān)鍵數(shù)字(為了確認(rèn)該數(shù)據(jù)段的確是MBR),并在MBR中檢測引導(dǎo)扇區(qū)的位置。這一扇區(qū)包含了LILO的開始部分,然后BIOS將其裝入內(nèi)存,開始執(zhí)行。注意我們現(xiàn)在已經(jīng)實(shí)現(xiàn)了從硬件和內(nèi)置軟件的范圍到實(shí)際軟件范圍的轉(zhuǎn)變,從有形范圍到無形范圍,也就是說從你可以接觸的部分到不可接觸的部分。下面就是LILO的責(zé)任了。它把自己其余的部分裝載進(jìn)來,在磁盤上找到配置數(shù)據(jù),這些數(shù)據(jù)指明從什么地方可以得到內(nèi)核,啟動時要通過什么選項(xiàng)。LILO接著裝載內(nèi)核到內(nèi)存并跳轉(zhuǎn)到內(nèi)核。通常,內(nèi)核以壓縮形式存儲,只有少量足以完成解壓縮任務(wù)的指令,也就是自解壓可執(zhí)行文件,是以非壓縮形式存儲的。因此,內(nèi)核的下一步工作是自解壓縮內(nèi)核鏡像。到這里,內(nèi)核就已經(jīng)完成了裝載的過程。下面是對所進(jìn)行步驟的簡要描述:1) CPU初始化自身,接著在固定位置執(zhí)行一條指令。2) 這條指令跳轉(zhuǎn)到BIOS中。3) BIOS找到啟動設(shè)備并獲取MBR,該MBR指向LILO。4) BIOS裝載并把控制權(quán)轉(zhuǎn)交給LILO。5) LILO裝載壓縮內(nèi)核。6) 壓縮內(nèi)核自解壓,并把控制權(quán)轉(zhuǎn)交給解壓的內(nèi)核。正如你所見到的,引導(dǎo)過程每一步都將你帶入更大量更復(fù)雜的代碼塊中,一直到最后成功地運(yùn)行了內(nèi)核為止。依賴于你計算層次的方式,CPU成為內(nèi)核引導(dǎo)程序的引導(dǎo)程序的引導(dǎo)程序的引導(dǎo)程序(CPU裝載BIOS,BIOS裝載LILO,LILO裝載壓縮內(nèi)核,壓縮內(nèi)核裝載解壓內(nèi)核;但是你可以考慮是否這些步驟都滿足引導(dǎo)程序的定義)。4.2 初始化Linux內(nèi)核在內(nèi)核成功裝入內(nèi)存(如果需要就解壓縮)以及一些關(guān)鍵硬件,例如已經(jīng)在低層設(shè)置過的內(nèi)存管理器(MMU,請參見第8章)之后,內(nèi)核將跳轉(zhuǎn)到start_kernel(19802行)。這個函數(shù)完成其余的系統(tǒng)初始化工作—實(shí)際上,幾乎所有的初始化工作都是由這個函數(shù)實(shí)現(xiàn)的。因此,start_kernel就是本節(jié)的核心。 start_kernel19802:__init標(biāo)示符在gcc編譯器中指定將該函數(shù)置于內(nèi)核的特定區(qū)域。在內(nèi)核完成自身初始化之后,就試圖釋放這個特定區(qū)域。實(shí)際上,內(nèi)核中存在兩個這樣的區(qū)域,.text.init和.data.init—第一個是代碼初始化使用的,另外一個是數(shù)據(jù)初始化使用的(可以在進(jìn)程間共享的代碼和字符串常量之類的“文本(Text)”是在可執(zhí)行程序中的“純區(qū)域”中使用的一個術(shù)語)。另外你也可以看到__initfunc和__initdata標(biāo)志,前者和__init類似,標(biāo)志初始化專用代碼,后者則標(biāo)志初始化專用數(shù)據(jù)。19807:如前所述,即使在多處理器系統(tǒng)中,在啟動時也只使用一個CPU。Intel稱之為引導(dǎo)程序處理器(bootstrap processor,簡稱為BSP),它在內(nèi)核代碼的某些地方有時也稱之為BP。BSP首次運(yùn)行這一行時,跳過后面的if語句,并減小boot_cpu標(biāo)志,從而當(dāng)其他CPU運(yùn)行到此處時,都要運(yùn)行if語句。等到其他CPU被激活執(zhí)行到這里時,BSP已經(jīng)在idle循環(huán)中了(本章稍后會更詳細(xì)地討論這個問題),initialize_secondary(4355行)負(fù)責(zé)把其他CPU加入到BSP中。這樣,其他CPU就不用執(zhí)行start_kernel的剩余部分了—這也是一件好事,因?yàn)檫@意味著不用再對許多硬件進(jìn)行冗余初始化等工作了。順便說一下,這種奇異的小小的改動只有對于x86是必需的;對于其他平臺,調(diào)用smp_init完全可以處理SMP設(shè)置的其他部分。因此,其他平臺的initialize_secondary的定義都是空的。19816:打印內(nèi)核標(biāo)題信息(20099行),這里顯示了有關(guān)內(nèi)核如何編譯的信息,包括在什么機(jī)器上編譯,什么時間編譯,使用什么版本的編譯器,等等。如果中間任何一步發(fā)生了錯誤,在尋找機(jī)器不能啟動的原因時查明內(nèi)核的來源是一個有用的線索。19817:初始化內(nèi)核自身的部分組件—內(nèi)存、硬件中斷、調(diào)度程序,等等。尤其是setup_arch函數(shù)(19765行)完成體系結(jié)構(gòu)相關(guān)的設(shè)置,此后在command_line(傳遞到內(nèi)核的參數(shù),在下面討論)、memory_start和memory_end(內(nèi)核可用物理地址范圍)中返回結(jié)果。下面這些函數(shù)都希望駐留在內(nèi)存低端,它們使用memory_start和memory_end來傳遞該信息。在函數(shù)獲得所希望的值后,返回值指明了新的memory_start的值。19823:分析傳給內(nèi)核的各種選項(xiàng)。parse_options函數(shù)(19707行,在隨后的“分析內(nèi)核選項(xiàng)”一節(jié)中討論)也設(shè)置了argv和envp的初值。19833:內(nèi)核運(yùn)行過程中也可以自行對所進(jìn)行的工作進(jìn)行記錄,周期性地對所執(zhí)行的指令進(jìn)行抽樣,并使用所獲得的結(jié)果更新表格。這在定時器中斷過程中通過調(diào)用x86_do_profile(1896行)來實(shí)現(xiàn),該部分將在第6章中介紹。如圖4-1中說明的那樣,這個表格把內(nèi)核劃分為幾個大小相同的范圍,并簡單跟蹤在一次中斷的時間內(nèi)每個范圍中運(yùn)行多少條指令。這種記錄當(dāng)然是非常粗糙的—甚至不是依據(jù)函數(shù)和行號進(jìn)行劃分的,而只是使用近似的地址—但是這樣代價很低,且快速、短小,而且有助于專家判斷最關(guān)鍵的問題。每個表格條目所涉及到地址的多少—還有問題發(fā)生地點(diǎn)的不確定性—可以通過簡單修改prof_shift(26142行)來調(diào)節(jié)。profile_setup(19076行,在本章中后面討論)可以讓你在啟動的時候設(shè)置prof_shift的值,這樣比為修改這個數(shù)字而重新編譯內(nèi)核要清晰方便得多。
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -