分類:
內(nèi)核
2012-03-14 00:55
2064人閱讀
收藏
舉報(bào)
從拿到Linux3.1.1版內(nèi)核源碼并搭建好閱讀環(huán)境開始,到現(xiàn)在大約已經(jīng)徘徊了兩個(gè)多月的時(shí)間,,期間google了大大小小的文章,,才剛剛理清了些許思路并找到了閱讀的切入口。對于內(nèi)核初學(xué)者來說一個(gè)好的指導(dǎo)比什么都重要,,有關(guān)Linux內(nèi)核學(xué)習(xí)的方法論可以參考fudan_abc寫的Linux內(nèi)核修煉之道,,作者以其深厚的內(nèi)核功底加上詼諧幽默的文字對讀者娓娓道來,這樣的感染力使得我?guī)缀跏且豢跉獠粩嗟目赐炅苏麄€(gè)專欄,,相信對于任何對內(nèi)核有強(qiáng)烈興趣的學(xué)習(xí)者一定有很多助益,。另外,,對初學(xué)者來說光有方法論是不夠的,特別是對于Linux
Kernel如此龐大的迷宮,,所以一份好的地圖在內(nèi)核學(xué)習(xí)中同樣舉足輕重,,Kconfig Makefile正扮演了如此重要的角色,特別是對于想要重點(diǎn)研究某一模塊或是子系統(tǒng)的學(xué)習(xí)者來說更是如此,,詳細(xì)參見以上專欄的具體文章——Kernel地圖:Kconfig與Makefile,,而對于Makefile
Kbuild體系介紹的更加具體的可以參見云松寫的Makefile預(yù)備知識(shí)/Kbuild體系,
當(dāng)然最權(quán)威的信息自然來自內(nèi)核文檔了,。根據(jù)我自己的實(shí)際情況,,其實(shí)我認(rèn)為并沒有必要去詳細(xì)分析每個(gè)makefile文件,因?yàn)樽詈蟮慕Y(jié)果是顯然的——一個(gè)
內(nèi)核映像由成百乃至上千個(gè)文件組成,,這樣的解剖工作量無疑是巨大的,,特別是對于像我這樣還沒有構(gòu)建大型系統(tǒng),甚至寫的最大的makefile文件都僅有寥
寥數(shù)行的初學(xué)者來說,,無疑是一個(gè)巨大的挑戰(zhàn),,因此通過google搜索前人所寫的關(guān)于某一模塊所依賴的源文件的文章可以讓我們把時(shí)間放在更加重要的源代碼
剖析上。
Kbuild/Makefile/Kconfig
根據(jù)個(gè)人的親身體會(huì),,閱讀Linux內(nèi)核對于新手來說首先要過的第一道坎便是源文件中大大小
小的CONFIG_XXXX標(biāo)識(shí),這對于廣大的像我一樣沒有接觸過驅(qū)動(dòng)開發(fā)/文件系統(tǒng)/網(wǎng)絡(luò)協(xié)議的學(xué)習(xí)者們無疑是對自信心的首個(gè)重大打擊,,不過幸運(yùn)的
是,,Linux內(nèi)核的發(fā)行版提供了豐富的文檔,在內(nèi)核學(xué)習(xí)的過程中碰到很多自己不熟悉的東西是很常見的現(xiàn)象,,因此學(xué)會(huì)查找合適的文檔對于學(xué)習(xí)將會(huì)事半功
倍,。
有關(guān)Kbuild/Makefile/Kconfig的文檔可參見目錄Documentation\kbuild,以下列出了該文檔中有關(guān)這三者的簡要概述:
- Makefile
Makefile總共包含五個(gè)部分,,分別為:①頂層Makefile文件,,②內(nèi)核配置文件,③在各個(gè)體系結(jié)構(gòu)下的makefile,,在目錄
arch/$(ARCH)中,,④一系列用于kbuild Makefile的通用規(guī)則,這些文件主要在scripts目錄中,,⑤kbuild
Makefiles,,這類文件大約有500個(gè)左右。
頂層Makefile讀取.config文件,,該文件主要在內(nèi)核的配置過程中生成,。有關(guān)內(nèi)核的具體配置可參見這里。
頂層的Makefile主要用來構(gòu)造兩個(gè)最主要的文件:vmlinux——固定內(nèi)核映像,,以及任意的模塊,,這種構(gòu)造過程是通過遞歸進(jìn)入內(nèi)核源代碼樹的子目
錄而完成的,。需要訪問的子目錄列表依據(jù)內(nèi)核的配置而定。頂層Makefile包含一個(gè)具體體系結(jié)構(gòu)下的Makefile文件,,該文件主要向頂層
Makefile提供指定體系結(jié)構(gòu)的信息,。
每一個(gè)子目錄都包含kbuild Makefile,其用來執(zhí)行由上級(jí)目錄傳遞下來的一些命令,。kbuild Makefile通過使用.config文件中包含的信息來構(gòu)造各種各樣的文件清單,,最終kbuild根據(jù)這些文件清單構(gòu)造任一內(nèi)置的或模塊化的目標(biāo)。
而scripts目錄中包含的一些Makefile.*文件中則包含了一系列的定義或是規(guī)則,,這些規(guī)則根據(jù)kbuild makefile構(gòu)造內(nèi)核,。
- Kbuild
這是從Linux2.6版本內(nèi)核開始采用的編譯系統(tǒng)。簡言之,,就是根據(jù)makefile生成的文件列表構(gòu)造內(nèi)置的或模塊化的目標(biāo),。
- Kconfig
kconfig文件就是用來存放組織成樹形結(jié)構(gòu)的一系列配置選項(xiàng)的配置數(shù)據(jù)庫。這些配置選項(xiàng)被組織成菜單條目的形式,,一個(gè)簡單的例子如下:- config MODVERSIONS
- bool "Set version information on all module symbols"
- depends on MODULES
- help
- Usually, modules have to be recompiled whenever you switch to a new
- kernel. ...
這
是一個(gè)簡單的配置菜單條目,,不過這也說明了所有的配置條目所應(yīng)有的特征:"config"引出一個(gè)新的配置選項(xiàng),接下來的行定義了一系列的屬性,,這些屬性
的類型可以是配置選項(xiàng),,輸入提示,依賴關(guān)系,,幫助文檔或是默認(rèn)值,,需要注意的是同一個(gè)配置菜單條目可以被定義多次,但每一次定義都僅有一個(gè)輸入提示,,并且
類型定義不能沖突,。絕大多數(shù)的條目都定義了一個(gè)配置選項(xiàng),而其他的條目為這些條目充當(dāng)依賴關(guān)系,。鑒于Kconfig文件重要性,,這里列出一些其常用語法:
——類型定義 :"bool"/"tristate"/"string"/"hex"/"int",表示用戶配置該選項(xiàng)時(shí)的輸入類型,。這里有兩種最基本的類型:tristate和string類型,,其他類型則基于這兩者。類型定義允許給出輸入提示,,比如:- bool "Networking support"
等價(jià)于- bool
- prompt "Networking support"
兩者都提示該配置菜單的輸入類型為bool型,,輸入之前將有一條提示為“Networking
support”。
——默認(rèn)值: "default" <expr> ["if" <expr>]
一個(gè)配置選項(xiàng)可以有任意數(shù)量的默認(rèn)值,。如果多個(gè)默認(rèn)值是可見的,,那么當(dāng)且僅當(dāng)?shù)谝粋€(gè)定義的默認(rèn)值處于活動(dòng)狀態(tài),即如果用戶不配置該選項(xiàng)那么該默認(rèn)值將被選
擇,。默認(rèn)值將不受所在菜單項(xiàng)的限制,,這意味著默認(rèn)值可以被定義在任何其他地方或被先前的定義所覆蓋,。可以通過添加"if"條件語句增加指定條件下存在的默
認(rèn)值,。
——類型定義+默認(rèn)值:"def_bool"/"def_tristate" <expr> ["if" <expr>]
這是類型定義和默認(rèn)值的簡寫方式,。同樣可以通過增加"if"語句添加指定條件下的類型及默認(rèn)值。
——依賴:"depends on" <expr>
這為本菜單項(xiàng)定義了一個(gè)依賴條件,,如果有多個(gè)依賴,,那么它們可以通過符號(hào)"&&"連接。依賴應(yīng)用于本菜單項(xiàng)從其定義處開始的所有其他屬性,。例如:- bool "foo" if BAR
- default y if BAR
等價(jià)于- depends on BAR
- bool "foo"
- default y
——幫助文檔:"help"或者"---help---"
該屬性定義了一個(gè)幫助文檔,,幫助文檔的末尾取決于其縮進(jìn)層次,換言之,,比幫助文檔開始的第一行有更小縮進(jìn)的一行指示整個(gè)幫助文檔結(jié)
束,。"---help---"與"help"在作用上并無差別,只不過"---help---"幫助開發(fā)者將配置選項(xiàng)從整個(gè)菜單項(xiàng)中清晰的分離出來,。
以上是對Kbuild/Makefile/Kconfig的簡要介紹,,其實(shí)理解這三者
的關(guān)鍵在于:因?yàn)闃?gòu)建的軟件是通過對源文件編譯得到的,而Linux內(nèi)核支持多種CPU架構(gòu),,這使得整個(gè)內(nèi)核包含成千上萬個(gè)每個(gè)架構(gòu)下各自所需的源文件,,
然而在形成內(nèi)核映像的過程中只需要從源文件中抽取出一部分針對某一種具體體系結(jié)構(gòu)的源文件,以及所有體系結(jié)構(gòu)下都需要的源文件(例如內(nèi)存管理/進(jìn)程調(diào)度
/網(wǎng)絡(luò)等)進(jìn)行編譯即可,,Kbuild
Makefile中的一部分實(shí)現(xiàn)的正是這樣的功能,,然而事實(shí)是Makefile中構(gòu)建每個(gè)目標(biāo)所需的源文件之間的相互依賴關(guān)系錯(cuò)綜復(fù)雜,并且上層的
Makefile還需要調(diào)用下層的Makefile,,這還有可能導(dǎo)致目標(biāo)覆蓋,期間還可能需要scripts目錄中的腳本文件輔助編譯,,還有
Makefile中一大堆預(yù)定義的對于初學(xué)者來說格式奇怪的環(huán)境變量/自動(dòng)化變量以及內(nèi)建函數(shù)等等,,最后鏈接順序?qū)τ谀承┠K來講具有嚴(yán)格的遞進(jìn)關(guān)系,否
則還有可能導(dǎo)致硬件的損壞,,可以看到,,僅僅內(nèi)核映像所需源文件的剖析過程就將耗費(fèi)大量的精力,所以這一部分工作建議通過搜索引擎完成,。而與我們的源文件有
關(guān)的Kconfig則應(yīng)該適當(dāng)了解,,否則正如上文所講,將對代碼的具體剖析過程造成極大的困擾,。以Page_32_types.h頭文件中定義的宏__PAGE_OFFSET中使用到的變量CONFIG_PAGE_OFFSET為例,,遇到這類配置類型的變量首先在對應(yīng)體系結(jié)構(gòu)下的目錄中找到Kconfig文件,注意我們的講解總是針對x86_32架構(gòu)的,,因此該Kconfig文件在arch\x86中,,之后的剖析過程中對配置類型的變量所在的目錄將不在贅述,,有關(guān)CONFIG_PAGE_OFFSET的菜單項(xiàng)如下所示:
- config PAGE_OFFSET
- hex
- default 0xB0000000 if VMSPLIT_3G_OPT
- default 0x80000000 if VMSPLIT_2G
- default 0x78000000 if VMSPLIT_2G_OPT
- default 0x40000000 if VMSPLIT_1G
- default 0xC0000000
- depends on X86_32
可
以看到該菜單項(xiàng)是一個(gè)類型為hex,即以16進(jìn)制格式顯示的整型且不可視(non-visible)變量(因?yàn)闆]有任何輸入提示,,這表現(xiàn)為hex之后沒有
帶雙引號(hào)的字符串,,也沒有prompt輸入提示符),如果預(yù)定義了表達(dá)式VMSPLIT_3G_OPT那么該值則為0xB000
0000,,后接三個(gè)默認(rèn)值均為在指定條件下PAGE_OFFSET可取的值,,最后一個(gè)沒有任何條件的默認(rèn)值為0xC000
0000,即當(dāng)我們使用默認(rèn)配置時(shí)PAGE_OFFSET的取值即為0xC000
0000,,最后看到depends on
X86_32語句,,說明該菜單項(xiàng)對X86_32配置選項(xiàng)具有依賴關(guān)系,但我們看到其后并未定義其他屬性,,因此直接忽略該依賴即可,。源文件中的
PAGE_OFFSET的含義是內(nèi)核在單個(gè)進(jìn)程的線性地址空間中的偏移量,這表明了在默認(rèn)情況下,,Linux內(nèi)核將占用x86架構(gòu)下0~4G虛擬內(nèi)存中最
高1G的線性地址空間,,同時(shí)也表明了在任意進(jìn)程的頁目錄項(xiàng)中從0x300(即768)開始的項(xiàng)均對應(yīng)的是內(nèi)核地址空間。
以上是有關(guān)Kbuild/Makefile/Kconfig的簡要介紹,,想要更加詳細(xì)
的了解這一部分的內(nèi)容可參考之前所列的資料,。可以看到與我們的源代碼剖析聯(lián)系最緊密的就是我們的Kconfig配置文件,,因此能夠大致理解Kconfig
文件是閱讀內(nèi)核源碼的最基本要求,,另外在x86\configs目錄下的i386_defconfig文件中也有對一部分CONFIG__XXXX標(biāo)識(shí)的
默認(rèn)配置。以上介紹的內(nèi)容僅僅只是我們內(nèi)核之旅開始的前奏,,接下來就將正式進(jìn)入我們的內(nèi)核世界中,。
加電啟動(dòng)
首先需要知道,內(nèi)存(DRAM)是一類易失性存儲(chǔ)設(shè)備,,從微觀上表現(xiàn)為,,泄漏電流的各種因素會(huì)導(dǎo)致DRAM單元在10~100毫秒時(shí)間內(nèi)失去電荷,因此存
儲(chǔ)系統(tǒng)必須周期性地通過讀出然后寫回來刷新存儲(chǔ)器的每個(gè)位,,這與SRAM不同,,只要有電,SRAM中存儲(chǔ)的信息就不需要刷新,,SRAM比之DRAM的優(yōu)點(diǎn)
還有存取速度快,,對光和電的噪音干擾不敏感,所有這些優(yōu)點(diǎn)的代價(jià)是SRAM單元比DRAM單元使用更多的晶體管,,更貴且功耗更大,;從宏觀上來講,其易失性
則表現(xiàn)為一旦斷電,,那么內(nèi)存中所存儲(chǔ)的信息都將丟失,。因此我們的內(nèi)核必須存儲(chǔ)在一類永久性的介質(zhì)中,,即使斷電也照樣可以保存信息,在PC上這樣的介質(zhì)就是
我們的硬盤,,它是通過將信息轉(zhuǎn)換為電信號(hào),,再將電信號(hào)轉(zhuǎn)換為磁場去磁化材料,這樣就將信息永久地保存下來,,當(dāng)然還可以是其他非易失性存儲(chǔ)設(shè)備如U盤,。然而
我們的程序在執(zhí)行之前都必須先被載入內(nèi)存,因?yàn)樵贑PU上執(zhí)行的指令都是在內(nèi)存中進(jìn)行尋址的,,繼而這就要求存在某種外力,,在開機(jī)啟動(dòng)時(shí)能夠?qū)?nèi)核“放入”
內(nèi)存并執(zhí)行相應(yīng)的初始化工作,其后將控制權(quán)轉(zhuǎn)移給內(nèi)核,,向上對用戶提供貼心的服務(wù),,向下則可以有效的管理硬件資源使得整個(gè)精彩紛呈的機(jī)器世界有效運(yùn)轉(zhuǎn),而
這種“外力”就是我們的基本輸入/輸出系統(tǒng)(Basic
Input/Output
System,,BIOS),,之所以稱其為BIOS是因?yàn)槠浒◣讉€(gè)中斷驅(qū)動(dòng)的低級(jí)過程。所有操作系統(tǒng)在啟動(dòng)時(shí)都需要借助這些過程對計(jì)算機(jī)硬件設(shè)備進(jìn)行初始
化,。因?yàn)锽IOS過程只能在實(shí)模式下運(yùn)行,,所以Linux一旦進(jìn)入保護(hù)模式就不再使用BIOS,而是為計(jì)算機(jī)上的每個(gè)硬件設(shè)備提供各自的設(shè)備驅(qū)動(dòng)程序,。
在開始啟動(dòng)時(shí),,有一個(gè)特殊的硬件電路在CPU的一個(gè)引腳上產(chǎn)生一個(gè)RESET邏輯
值,CPU在識(shí)別出RESET信號(hào)后將數(shù)據(jù)總線設(shè)為高阻抗?fàn)顟B(tài),,地址線強(qiáng)行設(shè)為1,,并禁用中斷。在這之后就將處理器的一些寄存器設(shè)成固定的值,,其中最重要
的兩個(gè)寄存器——CS段寄存器被置為0xf000,,EIP指令指針寄存器為0x0000 fff0,因?yàn)榇藭r(shí)CR0寄存器中的PE位還未被置位,,因此處于實(shí)模式狀態(tài)下,對指令的地址計(jì)算是通過在16位的CS段寄存器內(nèi)容最右邊補(bǔ)齊一個(gè)0從而形成一個(gè)小段,,再加上IP指令指針寄存器中的內(nèi)容(EIP寄存器的低16位),,用一個(gè)形象的公式即表示為:CS*16+IP,,最終得到地址值為0xf
fff0,。注意這個(gè)地址尋址的第一條指令存放在BIOS中,而又因?yàn)锽IOS被固化在ROM中,,并且對ROM中的指令進(jìn)行尋址則需要使用32位地址(原因請參考PC存儲(chǔ)器一文),,聯(lián)系到之前所介紹的,,CPU將地址線強(qiáng)行設(shè)為1,最終形成的物理地址即為0xffff
fff0,。形成該地址之后,,CPU將其交給MCH(南橋芯片-相當(dāng)于一個(gè)分配器,根據(jù)不同的地址映射分配給不同的設(shè)備去處理),,MCH會(huì)決定這個(gè)地址要分配給ICH(北橋芯片-解碼器),,最終這個(gè)地址被解析到BIOS的ROM里面的地址。
而BIOS實(shí)際上大致執(zhí)行以下4個(gè)操作:
- 對硬件執(zhí)行一系列測試,,用來檢測現(xiàn)在有哪些設(shè)備以及這些設(shè)備是否正常運(yùn)轉(zhuǎn),,這個(gè)階段被稱為POST(Power-On-Self-Test,,加電自檢),。
- 初始化硬件設(shè)備,,這個(gè)階段保證所有的硬件設(shè)備操作不會(huì)引起IRQ(中斷請求)線與I/O端口的沖突,最后顯示系統(tǒng)中安裝的所有PCI設(shè)備的一個(gè)列表,。
- 搜索一個(gè)操作系統(tǒng)來啟動(dòng)。這個(gè)過程可以根據(jù)用戶設(shè)置的順序依次進(jìn)行訪問系統(tǒng)中的軟盤,、硬盤以及CD-ROM的第一個(gè)扇區(qū)(即引導(dǎo)扇區(qū)),通常是在開機(jī)時(shí)按del鍵進(jìn)入BIOS的設(shè)置界面,,但也可能是其他鍵,,視各個(gè)具體的PC而定。
- 按上述訪問次序找到一個(gè)有效設(shè)備后,,即將第一個(gè)扇區(qū)的內(nèi)容拷貝到RAM中物理地址0x0000 7c00處,,隨后跳轉(zhuǎn)到該地址開始執(zhí)行剛剛加載的代碼。
有關(guān)BIOS啟動(dòng)過程更詳細(xì)的介紹可以參考BIOS啟動(dòng)過程——硬件檢測及初始化淺析一文,。想要學(xué)習(xí)BIOS編程的可以參考這里,。
引導(dǎo)裝入過程(boot loader)
引導(dǎo)裝入程序是由BIOS裝載,,且用來把操作系統(tǒng)的內(nèi)核映像裝載到RAM中所執(zhí)行的一個(gè)程序,,該可執(zhí)行過程存放在硬盤的第0個(gè)磁道第0個(gè)扇區(qū)上,由于總共
只需占用較少(512字節(jié))的存儲(chǔ)空間,,故也可稱之為引導(dǎo)記錄,,除此之外該引導(dǎo)記錄還包括64字節(jié)的分區(qū)表以及2個(gè)字節(jié)標(biāo)識(shí)有效引導(dǎo)記錄結(jié)尾的標(biāo)簽
(0x55
0xAA)。但從Linux2.6開始不再執(zhí)行這樣的引導(dǎo)裝入程序,這一點(diǎn)可以通過從header.S文件剖析得知(有關(guān)Linux下GAS格式的匯編語
法可參考GAS學(xué)習(xí)系列):
- BOOTSEG = 0x07C0
- SYSSEG = 0x1000
-
- .code16
- .section ".bstext", "ax"
-
- .global bootsect_start
- bootsect_start:
-
- # Normalize the start address
- ljmp $BOOTSEG, $start2
-
- start2:
- movw %cs, %ax
- movw %ax, %ds
- movw %ax, %es
- movw %ax, %ss
- xorw %sp, %sp
- sti
在
全局標(biāo)號(hào)bootsect_start后執(zhí)行一個(gè)長跳轉(zhuǎn)指令ljmp
$BOOTSEG,$start2,,因?yàn)锽OOTSEG的值為0x07C0,并且當(dāng)前仍處于實(shí)模式下,,因此該指令表示轉(zhuǎn)移到段基址為
0x7C00(seg*16),,偏移為start2地址處,聯(lián)系到開機(jī)啟動(dòng)時(shí)第一個(gè)扇區(qū)的內(nèi)容被BIOS拷貝到物理地址0x0000
7C00處,,所以該長跳轉(zhuǎn)指令實(shí)際表示轉(zhuǎn)移到本段start2標(biāo)號(hào)處繼續(xù)執(zhí)行,,可以看到在start2標(biāo)號(hào)后的指令將cs段寄存器中的內(nèi)容依次填充至
ds/es/ss寄存器中,接著將sp堆棧指針寄存器內(nèi)容清零并啟用中斷,。
- cld
-
- movw $bugger_off_msg, %si
-
- msg_loop:
- lodsb
- andb %al, %al
- jz bs_die
- movb $0xe, %ah
- movw $7, %bx
- int $0x10
- jmp msg_loop
可以發(fā)現(xiàn)上述代碼段的作用即是調(diào)用BIOS中斷例程在屏幕上顯示標(biāo)識(shí)為bugger_off_msg的字符串內(nèi)容,,有關(guān)BIOS中斷內(nèi)容可以參考這里。bugger_off_msg的定義如下:
- bugger_off_msg:
- .ascii "Direct booting from floppy is no longer supported.\r\n"
- .ascii "Please use a boot loader program instead.\r\n"
- .ascii "\n"
- .ascii "Remove disk and press any key to reboot . . .\r\n"
- .byte 0
該字符串告知用戶“已不再支持從軟盤直接啟動(dòng)的方式,,請代之以使用一個(gè)boot loader程序,,移除磁盤并按任意鍵重啟”。接著顯示完該字符后跳轉(zhuǎn)到bs_die標(biāo)號(hào)處繼續(xù)執(zhí)行,,該代碼段如下所示:
- bs_die:
- # Allow the user to press a key, then reboot
- xorw %ax, %ax
- int $0x16
- int $0x19
-
- # int 0x19 should never return. In case it does anyway,
- # invoke the BIOS reset code...
- ljmp $0xf000,$0xfff0
以上代碼等待用戶按鍵之后即刻重啟,。實(shí)際上雖然header.S文件中自帶bootloader,,但如果內(nèi)核從硬盤啟動(dòng)該段代碼無論如何都不會(huì)被執(zhí)行,而內(nèi)核的維護(hù)者也不再維護(hù)這段引導(dǎo)記錄,,關(guān)于這一點(diǎn)也可以從鏈接腳本文件的設(shè)置中看出,。
- OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386")
- OUTPUT_ARCH(i386)
- ENTRY(_start)
-
- SECTIONS
- {
- . = 0;
- .bstext : { *(.bstext) }
- .bsdata : { *(.bsdata) }
-
- . = 497;
- .header : { *(.header) }
- .entrytext : { *(.entrytext) }
- .inittext : { *(.inittext) }
- .initdata : { *(.initdata) }
- __end_init = .;
- ......
- }
以上文件為arch\x86\boot目錄下的setup.ld,可以發(fā)現(xiàn)鏈接腳本指定的入口為_start標(biāo)號(hào)而非bootsect_start,,這就好比在C/C++中指定main()函數(shù)為入口點(diǎn),。事實(shí)上當(dāng)Linux內(nèi)核從_start標(biāo)號(hào)處開始執(zhí)行后便再也不會(huì)跳入bootsect_start標(biāo)號(hào)執(zhí)行。在這里簡單提一下鏈接腳本的語法:在SECTIONS{}內(nèi)設(shè)置最終生成的文件中各個(gè)節(jié)區(qū)的屬性,,.
= 0表示.bstext以及.bsdata節(jié)區(qū)中的代碼及數(shù)據(jù)被加載至偏移0處,。每個(gè)節(jié)區(qū)的描述格式都是“節(jié)區(qū)名 : { 組成 }”,例如.bstext : { *(.bstext) } ,,左邊表示最終生成的文件的.bstext 段,,右邊表示所有目標(biāo)文件的.bstext 段,意思是最終生成的文件的.bstext節(jié)區(qū) 由各目標(biāo)文件的.bstext節(jié)區(qū) 組合而成,,有關(guān)鏈接腳本更詳細(xì)的介紹參見GNU-ld鏈接腳本淺析。
聯(lián)系上述引導(dǎo)裝入程序中開始處的.section
".bstext",
"ax"指令,,我們知道上述代碼位于被載入內(nèi)存的整個(gè)內(nèi)核映像的起始處,,注意雖然這些指令不會(huì)被執(zhí)行,,但其仍然將會(huì)被載入內(nèi)存,并且在這個(gè)512字節(jié)的引
導(dǎo)記錄中存放的數(shù)據(jù)仍是有用的,,這些參數(shù)將被用來初始化setup_header結(jié)構(gòu)體(位于arch\x86\include\asm
\Bootparam.h文件中),,如下所示:
- .section ".header", "a"
- .globl hdr
- hdr:
- setup_sects: .byte 0
- root_flags: .word ROOT_RDONLY
- syssize: .long 0
- ram_size: .word 0
- vid_mode: .word SVGA_MODE
- root_dev: .word 0
- boot_flag: .word 0xAA55
-
- # offset 512, entry point
這些參數(shù)位于.header節(jié)區(qū)中,由之前的鏈接腳本文件得知這個(gè)節(jié)區(qū)在整個(gè)引導(dǎo)記錄的偏移量為497,,而其中的變量將總共占用1+2+4+2*4=15個(gè)字節(jié)的空間,,因而最終構(gòu)成512字節(jié)的引導(dǎo)記錄。另外從標(biāo)識(shí)整個(gè)有效引導(dǎo)記錄結(jié)尾的標(biāo)簽的匯編指令boot_flag: .word 0xAA55以
及最后一行的注釋也可看出,。另外要注意,,由于x86體系結(jié)構(gòu)采用小端法存放數(shù)據(jù),因此0xAA55最后存放的形式從低到高依次為0x55,,0xAA,,與我
們之前所描述的并不沖突。而這些參數(shù)的填充從注釋中可以看出是由build.c文件完成的,,我們將放在后文介紹這種參數(shù)的具體設(shè)置,。
前面提到過,bootloader由BIOS加載,,并用于負(fù)責(zé)將內(nèi)核映像裝入內(nèi)存,,然
而Linux2.6開始不再執(zhí)行該代碼,那么裝載內(nèi)核映像的重大責(zé)任由誰來承擔(dān),?在x86體系中,,這便是眾所周知的LInux
LOader(LILO)或是廣泛使用的GRand Unified Bootloader(GRUB)。
事實(shí)上,,由于硬盤容量的飛速發(fā)展,,通常是將一個(gè)硬盤劃分成若干個(gè)“分區(qū)”,從而可以把
一個(gè)物理硬盤看成是若干個(gè)邏輯磁盤,。這樣在每個(gè)邏輯磁盤上都可以存放一個(gè)操作系統(tǒng)映像,,并且每個(gè)邏輯磁盤的第一個(gè)扇區(qū)仍然作為引導(dǎo)扇區(qū)。顯然這些引導(dǎo)扇區(qū)
在物理上已經(jīng)不再是整個(gè)硬盤的第一個(gè)扇區(qū)了(即整塊硬盤上的第0個(gè)磁道第0個(gè)扇區(qū)),,在這種情況下,,整個(gè)硬盤的第一個(gè)扇區(qū)被獨(dú)立出來,不屬于任何一個(gè)邏輯
磁盤,。但BIOS在執(zhí)行POST操作以及初始化后還是從這個(gè)扇區(qū)引導(dǎo),,因而在該扇區(qū)中存放的引導(dǎo)程序又被稱為主引導(dǎo)記錄(Master
Boot Record,MBR),。有關(guān)MBR更詳細(xì)的信息可以參考操作系統(tǒng)裝載過程及BootSector的匯編語言實(shí)現(xiàn)一文,。
從表面上看加載過程是分成兩個(gè)階段進(jìn)行的,即首先加載MBR塊中的引導(dǎo)過程,隨后再加
載位于某個(gè)分區(qū)下第一個(gè)扇區(qū)的針對具體操作系統(tǒng)的次引導(dǎo)過程,。但我們從上述bootloader過程的剖析來看,,除了用到其中的部分?jǐn)?shù)據(jù)之外并不執(zhí)行任何
指令,這是因?yàn)楝F(xiàn)代的操作系統(tǒng)將所有的引導(dǎo)加載任務(wù)都放入MBR中由其統(tǒng)一執(zhí)行,。該執(zhí)行過程分為如下兩個(gè)部分:
- MBR包括一個(gè)小的引導(dǎo)程序,,由BIOS將其裝入RAM中物理地址0x0000 7c00處,這個(gè)引導(dǎo)程序通過建立實(shí)模式棧將MBR的第二部分載入內(nèi)存,。
- 第二部分依次從磁盤讀取可用操作系統(tǒng)的映射表,,并提供給用戶一個(gè)提示符,當(dāng)用戶選擇需要被載入的內(nèi)核后(或經(jīng)過一段時(shí)間的延遲自動(dòng)選擇一個(gè)默認(rèn)值),,引導(dǎo)程序?qū)⑾鄳?yīng)分區(qū)的內(nèi)核映像直接拷貝到內(nèi)存中(包括該分區(qū)中第一個(gè)扇區(qū)的引導(dǎo)記錄),。
其中引導(dǎo)扇區(qū)裝載內(nèi)核映像的步驟又進(jìn)一步細(xì)分如下:
- 調(diào)用一個(gè)BIOS例程顯示”Loading“信息
- 調(diào)用BIOS例程裝入內(nèi)核映像的初始部分,即將內(nèi)核映像的第一個(gè)512字節(jié)從地址0x0009 0000處開始裝入內(nèi)存,。
- 同樣通過調(diào)用BIOS例程裝載其余的內(nèi)核映像,,并把內(nèi)核映像放入從地地址0x0001 0000(小內(nèi)核映像)或者從高地址0x0010 0000(大內(nèi)核映像)開始的RAM中。
完成內(nèi)核映像的裝載之后,,控制權(quán)即正式轉(zhuǎn)交給內(nèi)核,。欲知后事如何,且聽下回分解,。
|