深入介紹Linux內核(八)
第五章
Linux內核體系結構
在本章這里,我們首先要介紹的是Linux內核的編制模式和體系結構,然后詳細描述了Linux內核原始碼目錄中組織形式和子目錄中各個(gè)代碼檔的主要功能,以及基本呼叫的層次關(guān)系等。接著(zhù)就是直接走入正題,從內核原始檔案Linux/目錄下的第一個(gè)檔Makefile開(kāi)始,對每一行代碼進(jìn)行詳細注釋說(shuō)明。本章內容可以看作是對內核原始碼的總結概述,也可以作為閱讀后續章節的參考資訊。對於較難理解的地方可以先跳過(guò),待閱讀到后面相關(guān)內容時(shí)再返回來(lái)參考本章內容。在閱讀本章之前請先復習或學(xué)習有關(guān)80X86保護模式執行方式工作原理。
一個(gè)完整可用的作業(yè) 充主要由4部分組成:硬體、作業(yè)系統內核、作業(yè)系統服務(wù)和用戶(hù)應用程式,圖5-l所示。用戶(hù)應用程式是指那些字處理程式、Internet瀏覽器程式或用戶(hù)自行編制的各種應用程式;作業(yè)系統服務(wù)程式是指那些向用戶(hù)提供的服務(wù)被看作是作業(yè)系統部分功能的程式。在Linux作業(yè)系統上,這些程式包括X視窗系統、shell命令解釋系統以及那些內核程式設計介面等系統程式;作業(yè)系統內核程既是本篇文章所感興趣的部分,它主要用於對硬件資源的抽象和存取調度。
在匯流排控制器控制下,8259A晶片可以處于程式設計狀態(tài)和操作狀態(tài),程式設計伏態(tài)是CPU使用IN或OUT指令對82 59A晶片進(jìn)行初始化程式設計的狀態(tài)。一旦完成了初始化程式設計,晶片即進(jìn)入操作狀態(tài),此時(shí)晶片即可隨時(shí)回應外部裝置提出的中斷請求(IRQ0 - IRQ15),同時(shí)系統還可以使用操作命令字隨時(shí)修改其中斷處理方式。透過(guò)中斷判優(yōu)選,晶片將選中當前最高優(yōu)先順序的中斷清求作為中斷服務(wù)對象,並透過(guò)CPU接腳INT通知CPU外中斷請求的到來(lái),CPU回應后,晶片從資料匯流排D7-D0將程式設計設定的當前服務(wù)對象的中斷號送出,CPU由此獲取對應的中斷向量值,並執行中斷服務(wù)程式。
5.4.3 中斷向量表
上節已指出CPU是根據中斷號獲取中斷向量值,即對應中斷服務(wù)程式的入口位址直。因此為了讓CPU由中斷號查找到對應得中斷向量,就需要在記憶體中建立一張查詢(xún)表,即中斷向量表(在32位元保護模式下該表稱(chēng)為中斷描述符表,見(jiàn)下面說(shuō)明)。80X86微機支持256個(gè)中斷,對應每個(gè)中斷需要安排一個(gè)中斷服務(wù)程式。在80X86真實(shí)模式執行方式下,每個(gè)中斷向量由4個(gè)位元組組成。這4個(gè)立元組指明了一個(gè)中斷服務(wù)程式的段值和段內偏移值。因此整個(gè)向量表的長(cháng)度為1024位元組。當80X86微機啟動(dòng)時(shí),ROM BIOS中的程式會(huì )在實(shí)體記
憶體開(kāi)冶位址0x0000 : 0x0000處初始化並設置中斷向量表,而各中斷的預設中斷服務(wù)程式則在BIOS中給出。由於中斷向量表中向量是按中斷號順序排列,因此給定一個(gè)中斷號N,那麼它對應的中斷向量在記憶體中的位置就是0x0000 : N*4,即對應的中斷服務(wù)程式入口位保存在實(shí)體記憶體0x0000 : N*4位置處。
在BIOS執行初始化操作時(shí),它設置了兩個(gè)8259A晶片支援的16個(gè)硬體中斷向量和BIOS提供的中斷號為0x10 - 0xlf 得中斷呼叫功能向量等。對於實(shí)際沒(méi)有使習的向量則填入臨時(shí)的啞中斷服務(wù)程式位址。以后在系統開(kāi)機載入作業(yè)系統時(shí)會(huì )根據實(shí)際需要修改某些中斷向量的值 列如,對於DOS作業(yè)系統,它會(huì )重新役置中斷0x20 – 0x2f的中斷向量值。而對於Linux系統,除了在剛開(kāi)始載入內核時(shí)需要用到BIOS提供的顯示和磁碟讀取操作中斷功能,在內核正常執行之前則會(huì )在setup.s程式中重新初始化8259 A晶片並且在head.s程式中重新設置一張中斷向量表(中斷描述符表)。完全拋棄BIOS所提供的中斷服務(wù)功能。
當Intel CPU執行在32位元保護模式下時(shí),需要使用中斷描述符表IDT (Interrupt Descriptor Table)來(lái)管理中斷或異常。IDT是Intel 8086- -80186CPU中使用的中斷向量表的直接替代物。其作用也類(lèi)似于中斷向量表,只是其中每個(gè)中斷描述符項中除了含有中斷服務(wù)程式位址以外,還包含有關(guān)特權級和描述符類(lèi)別等資訊。Linux作業(yè)系統工作於80X86的保護模式下,因此它使用中斷描述符表安來(lái)設置和保存各中斷的“向量”資訊。
5.4.4Linux內核的中斷處理
於于Linux內核來(lái)說(shuō),中斷信號通常分兩類(lèi) :硬體中斷和軟件中斷(異常)。每個(gè)中斷是由 0-255 之間的一個(gè)數字來(lái)標識。對於中斷int0- -int3l(0x00- -0xff),每個(gè)中斷的功能由Intel公司固定設定或保留用,屬於軟體中斷,但Intel公司稱(chēng)之為異常。因為這些中斷是在CPU執行指令時(shí)探測到異常晴況而引起的。通常還可分為故障(Fault)和陷阱(traps)兩類(lèi)。中斷
int32- -int255(0x20- -0xff)可以由用戶(hù)自己設定。所有中斷的分類(lèi)以及執行后CPU的動(dòng)作方式見(jiàn)表5-1所示。
Linux內核的主要用途就是為了與電腦硬件進(jìn)行交流,實(shí)現對硬件部件的程式設計控制和介面操作,調度對硬件資源的存取,並為電腦上的用戶(hù)程式提供一個(gè)高級的執行環(huán)境和對硬件的虛擬介面。
在本章內容中,我們是基于Linux 0.12版的內核原始碼,簡(jiǎn)明地描述Linux內核的基本體系結構、主要構成模組。然后對原始碼中出現的幾個(gè)重要資料結構進(jìn)行說(shuō)明。最后描述了建構Linux 0.12 內核編譯實(shí)驗環(huán)境的方法。
5.1 Linix內核模式
目前,作業(yè)系統內核的結構模式主要可分為整體式的單內核模式和層次式的微內核模式。而本文章所注釋的Linux 0.12內核,則是採用了單內核模式。單內核模式的主要優(yōu)點(diǎn)是內核代碼結構緊湊,執行速度快,不足之處主要是層次結構性不強。
在單內核模式的系統中,作業(yè)系統所提供服務(wù)的流程為:應用主程序使用指定的參數值執行系統呼叫指令(int x80),使CPU從用戶(hù)態(tài)(User Mode)切換到核心態(tài)(Kernel Model),然后作業(yè)系統根據具體的參數值呼叫特定的系統呼叫服務(wù)程式,而這些服務(wù)程式則根據需要再呼叫底層的一些支援函數以完成特定的功能。在完成了應用程式所要求的服務(wù)后,作業(yè)系統又使CPU從核心態(tài)切換回用戶(hù)態(tài),從而返回到應用程式中繼續執行后面的指令。因此概要地講,單內核模式的內核也可粗略地分為三個(gè)層次:呼叫服務(wù)的主程序層、執行系統呼叫的服務(wù)層和支援系統呼叫的底層函數。見(jiàn)圖5-2所示。
5.2 Linux系統體系結構
Linux內核主要由5個(gè)模組構成,它們分別是:行程調度模組、記憶體管理模組、檔案系統模組、行程間通信模組和網(wǎng)路介面模組。
行程調度模組用來(lái)負責控制行程對CPU資源的使用。所採取的調度策略是各行程能夠公平合理地存取CPU,同時(shí)保證內核能及時(shí)地執行硬件操作,記憶體管理模組用於確保所有行程 能夠安全地共用機器主記憶體區,同時(shí),記憶體管理模組還支援虛擬記憶體管理方式,使得Linux支援行程使用比實(shí)際記憶體空間更多的記憶體容量。並可以利用檔案系統把暫時(shí)不用的記憶體資料塊交換到外部?jì)Υ嫜b置上去,當需要時(shí)再交換回來(lái)。檔案系統模組用於支援對外部裝置的驅動(dòng)和儲存。虛擬檔案系統模組透過(guò)向所有的外部?jì)Υ嫜b置提供一個(gè)通用的檔介面,隱藏了各種硬件裝置的不同細節。從而提供並支援與其他作業(yè)系統相容的多種檔案系統格式。行程間通信模塊子系統用於支援多種行程間的資訊交換方式。網(wǎng)路介面模塊提供對多種網(wǎng)絡(luò )通信標準的存取並支援許多網(wǎng)路硬體。
這幾個(gè)模塊之間的依賴(lài)關(guān)系見(jiàn)圖5-3所示。其中的連線(xiàn)代表它們之間的依賴(lài)關(guān)系,虛線(xiàn)和虛框部分表示Linux 0.12中還未實(shí)現的部分(從Linux 0.95版才開(kāi)始逐步實(shí)現虛擬檔案系統,而網(wǎng)路介面的支援到0.96版才有)。
由圖可以看出,所有的模組都與行程調度模組存在依賴(lài)關(guān)系。因為它們都需要依靠行程調度程式來(lái)掛起(暫停)或重新執行它們的行程。通常,一個(gè)模組會(huì )在等待硬件操作期間被掛起,而在操作完成后才可繼續執行。例如,當一個(gè)行程試圖將一資料塊寫(xiě)到軟碟上去時(shí),軟碟驅動(dòng)程式就可能在啟動(dòng)軟碟旋轉期問(wèn)將該行程置為掛起等待狀態(tài),而在軟碟進(jìn)入到正常轉速后再使得該行程能繼續執行。另外3個(gè)模組也是由於類(lèi)似的原因而與行程調度模組存在依賴(lài)關(guān)系。
其他幾個(gè)模組的依賴(lài)關(guān)系有些不太明顯,但同樣也很重要。行程調度子系統需要使用記憶體管理來(lái)調整一特定行程所使用的實(shí)體記憶體空間。行程間通信子系統則需要依靠記憶體管理器來(lái)支援共用記憶體通信機制。這種通信機制允許兩個(gè)行程存取記憶體的同一個(gè)區域以進(jìn)行行程間資訊的交換。虛擬檔案系統也會(huì )使用網(wǎng)路介面來(lái)支援網(wǎng)路檔案系統(NFS) ,同樣也能使用記憶體管理子系統提供記憶體虛擬碟(ramdisk)裝置。而記憶體管理子系統也會(huì )使用檔案系統來(lái)支援記憶體資料塊的交換操作。
若從單內核模式結構模型出發(fā),我們還可以根據Linux 0.12內核原始碼的結構將內核主要模組繪制成圖5-4所示的框圖結構。
其中內核級中的幾個(gè)方框,除了硬體控制方框以外,其他粗線(xiàn)方框分別對應內核原始碼的目錄組織結構。
除了這些圖中已經(jīng)給出的依賴(lài)關(guān)系以外,所有這些模組還會(huì )依賴(lài)於內核中的通用資源。這些資源包括內核所有子系統都會(huì )呼叫的記憶體分配和收回函數、列印警告或出錯非訊函數以及一些系統除錯函數。
5.3 Linux內核對記憶體的管理和使用
本節首先說(shuō)明Linux 0.12系統中比較直觀(guān)的實(shí)體記憶體使用情況,然后結合Linux 0.12內核中的應用情況,再分別概要描述記憶體的分段和分頁(yè)管理機制以及CPU多工操作和保護方式。最后我們再綜合說(shuō)明Linux 0.12系統中內核代碼和資料以以及各個(gè)任務(wù)的代碼和資料在虛擬位址、線(xiàn)性位址和實(shí)體位址之間的對應關(guān)系。
5.3.1實(shí)體記憶體
在linux0.12內核中,為了有效地使用機器中的實(shí)體記憶體,在系統初始化階段記憶體被劃分成幾個(gè)功能區域,見(jiàn)圖5-5所示
其中,Linux內核程式佔據在實(shí)體記憶體的開(kāi)始部分,接下來(lái)是供硬碟或軟碟等區塊裝置使用的高速緩沖區部分(其中要扣除顯示卡記憶體和ROM BIOS所佔用的記憶體位址范圍640K- -l MB) 。當一個(gè)行程子要讀取區塊裝置中的數據時(shí),系統會(huì )首先把數據讀到高速緩沖區中;當有數據需要寫(xiě)到區塊裝置上去時(shí),系統也是先將數據放到高速緩沖區中,然后由區 塊裝置驅動(dòng)程式寫(xiě)到相應的裝置上。記憶體的最后部分是供所有程式可以隨時(shí)申請和使用的主記憶體區,內核程式在使用主記憶體區時(shí),也同樣首先要向內核記憶體管理模組提出申請,並在申請成功后才能使用。對於含有RAM虛擬碟的系統,主記憶體區頭部還要劃去一部分,供虛擬盤(pán)存放數據。
由于電腦系統中所含的實(shí)際實(shí)體記憶體容有限,因此CPU中通常都提供了記憶體管理機制對系統中的記憶體進(jìn)行有效的管理。在Intel 80386及以后的CPU中提供了兩種記憶體管理(位址變換)系統:記憶體分段系統(Segmentation Systen)和分頁(yè)系統(Paging System)。其中分頁(yè)管理系統是可選擇的,由系統程式師透過(guò)程式設計來(lái)確定是否採用。為了能有效地使用實(shí)體記憶體,Linux系統同寺採用了記憶體分段和分頁(yè)管理機制。
5.3.2 記憶體位址空間概念
Linux0.12內核中,在進(jìn)行位址映射操作時(shí),我們需要首先分清3種位址以及它們之間的變換概念:a.程式(行程)的虛擬和邏輯位址:b.CPU的線(xiàn)性位址:C.實(shí)際實(shí)體記憶體地址。
虛擬位址(Virtual Address)是指由程式產(chǎn)生的由段選擇符和段內偏移位址兩個(gè)部分組成的位址。因為這兩部分組成的位址並沒(méi)有直接用來(lái)存取實(shí)體記憶體,而是需要透過(guò)分段位址變換機制處理或映射后才對應到實(shí)體記憶體位址上,因此這種位址被稱(chēng)為虛擬位址。虛擬位址空間 GDT映射的全域位址空間和由LDT映射的區域位址空間組成。選擇符的索引部分由13個(gè)Bit位表示,加上區分GDT和LDT的l個(gè)Bit位,因此Intel 80X86 CPU共可以索引16384個(gè)選擇符。若每個(gè)段的長(cháng)度都取最大值4G,則最大虛擬位址空間范圍是16384*4G=64T。
邏輯位址(Logical Address)是指由程式產(chǎn)生的與段相關(guān)的偏移位址部分。在Intel保護模式下即是指程式執行代碼段限長(cháng)的偏移位址(假定代碼段、資料段完全一樣)。應用程式師僅需與邏輯位址打交道,而分段和分頁(yè)機制對他來(lái)說(shuō)是完全透明的,僅由系統程式設計人員涉及 。不過(guò)有些資料並不區分邏輯位址和虛擬位址的概念,而是將它們統稱(chēng)為邏輯位址。
線(xiàn)性位址(Linear Address)是虛擬位址到體位址變換之間的中間層,是處理器可定址的記憶體空間(稱(chēng)為線(xiàn)性位址空間)中的位址。程式碼會(huì )產(chǎn)生邏輯位址,或者說(shuō)是段中的偏移位址,加上相應段的基底位址就生成了一個(gè)線(xiàn)性位址。如果啟用了分頁(yè)機制,那麼線(xiàn)性位址可以再經(jīng)變換以產(chǎn)生一個(gè)實(shí)體位址。若沒(méi)有啟用分頁(yè)機制,那麼線(xiàn)性位址直接就是實(shí)體位址。Intel 80386的線(xiàn)性位址空間容良為4G。
實(shí)體位址(Physical Address)是指出現在CPU外部位址匯流排上的定址實(shí)體記體的位址信號,是位址變換的最終結果地址。如果啟用了分頁(yè)機制,那么線(xiàn)性位址會(huì )使用頁(yè)目錄和頁(yè)表中的項變換成實(shí)體位址。如果沒(méi)有啟用分員機制,那麼線(xiàn)性位址就直接成為實(shí)體位址了。
虛擬儲存(或虛擬記憶體) (Virtual Memory)是指電腦呈現出要比實(shí)際擁有的記憶體大得多的記憶體量。因此它允許程式師編制並執行比實(shí)際系統擁有的記憶體大得多的程式。這使得許多大型專(zhuān)案也能夠在具有有限記憶體資源的系統上實(shí)現。一個(gè)很恰當的的比喻是:你不需要很長(cháng)的軌道就可以讓一列火車(chē)從上海開(kāi)到北京。你只需要足夠長(cháng)的鐵軌(比如說(shuō)3公里)就可以完成這個(gè)任務(wù)。採取的方法是把后面的鐵軌立刻鋪到火車(chē)的前面,只要你的操作足夠快並能滿(mǎn)足要求,列車(chē)就能象在一條完整軌道上執行。這也就是虛擬記憶體管理需要完成的任務(wù)。在Linux 0.12內核中,給每個(gè)程式(行程)都劃分了總容量為64MB的虛擬記憶體空間。因此程式的邏輯位址范圍是Ox0000000到0x4000000。
如上所述,有時(shí)我們也把邏輯位址稱(chēng)為虛擬位址。因為邏輯位址與虛擬記憶體空間的概念類(lèi)似,并且是與實(shí)際實(shí)體記憶體容量無(wú)關(guān)。
5.3.3 記憶體分段機制
在記憶體分段系統中,一個(gè)程式的邏輯位址透過(guò)分段機制自動(dòng)地映射(變換)到中間層的4GB (2³²)線(xiàn)性位址空間中。程式每次對記憶體的引用都是對記憶體段中記憶體的參照引用。程 式引用一個(gè)記憶體位址時(shí),透過(guò)把相對應的段基址加到程式師看得見(jiàn)的邏輯位址上就形成了一個(gè)對應的線(xiàn)性位址。此時(shí)若沒(méi)有啟用分頁(yè)機制,則該線(xiàn)性位址被送到CPU的外部位址匯流排上,用於直接定址對應的實(shí)體記憶體。見(jiàn)圖5-6所示。
圖5-6 虛擬位址(邏輯位址)到實(shí)體位址的變換過(guò)程
CPU進(jìn)行位址變換(映射)的主要目的是為了解決虛擬記憶體空間到實(shí)體記憶體空間的映射問(wèn)題。虛擬記憶體空間的含義是指一種利用二級或外部?jì)Υ婵臻g,使程式能不受實(shí)際實(shí)體記憶體量限制而使用記憶體的一種方法。通常虛擬記憶體空間要比實(shí)際實(shí)體記憶體量大得多。
那麼虛擬儲存管理是怎樣實(shí)現的呢? 原理與上述列車(chē)運行的比喻類(lèi)似。首先,當一個(gè)程式需要使用一塊不存在的記憶體時(shí)(也即在記憶體頁(yè)表項中已標出相應記憶體頁(yè)面不在記憶體中),CPU就需要一種方法來(lái)得知這個(gè)情況。這是透過(guò)80386的頁(yè)錯誤異常中斷來(lái)實(shí)現的。當一個(gè)行程引用一個(gè)不存在頁(yè)面中的記憶體位址時(shí),就會(huì )觸發(fā)CPU產(chǎn)生頁(yè)出錯異常中斷,並把引起中斷的線(xiàn)性位址放到CR2控制暫存器中。因此處理該中斷的過(guò)程就可以知道發(fā)生頁(yè)異常的確切位址,從而可以把行程要求的頁(yè)面從二級儲存空間(比如硬碟上)載入到實(shí)體記憶體中。如果此時(shí)實(shí)體記憶體已經(jīng)全部佔用,那麼可以借助二級儲存空間的一部分作為交換緩沖區(Swapper)把記憶體中暫時(shí)不使用的頁(yè)面交換到二級緩沖區中,然后把要求的頁(yè)面調入記憶體中。這也就是記憶體管理的缺頁(yè)載入機制,在Linux 0.12內核中是在程式mm/memory.c中實(shí)現。
Intel CPU使用段(Segment)的概念來(lái)對程式進(jìn)行定址。每個(gè)段定義了記憶體中的某個(gè)區域以及存取的優(yōu)先順序等資訊。假定大家知曉真實(shí)模式下記憶體定址原理,現在我們根據CPU真實(shí)模式和保護模式下定址方式的不同,用比較的方法來(lái)簡(jiǎn)單說(shuō)明32位元保護模式執行機制下記憶體定址的主要特點(diǎn)。
在真實(shí)模式下,定址一個(gè)記憶體位址主要是使用段和偏移值,段值被存放在段寄存器中(例如ds) ,並且段的長(cháng)度被固定為64KB。段內偏移位址存放在任意一個(gè)可用於定址的寄存器中(例如si)。因此,根據段寄存器和偏移寄存器中的值,就可以算出實(shí)際指向的記憶體位址,見(jiàn)圖5-7(a)所示。
而在保護模式執行方式下,段寄存器中存放的不再是被定址段的基底位址,而是一個(gè)段描述符表(Segment Descriptor Table)中某一描述符項在表中的索引值。索引值指定的段描述符項中含有需要定址的記憶體段的基底位址、段的長(cháng)度值和段的存取特權級別等信息。定址的記憶體位置是由該段描述符項中指定的段基底位址值與一個(gè)段內偏移值組合而成。段的長(cháng)度可變,由描述符中的內容指定??梢?jiàn),和真實(shí)模式下的定址相比,段寄存器值換成了段描述符表中相應段描述符的索引值以及段表選擇位和特權級,稱(chēng)為段選擇符(Segment Selector) ,但偏移值還是使用了原真實(shí)模式下的概念。這樣,在保護模式下定址一個(gè)記憶體位址就需要比真實(shí)模式下多一道手續,也即需要使用段描述符表。這是由於在保護模式下存取一個(gè)記憶體段需要的資訊比較多,而一個(gè)16位的段寄存器放不下這麼多內容。示意圖見(jiàn)圖5-7 (b)所示。注意,如果你不在一個(gè)段描述符中定義一個(gè)記憶體線(xiàn)性位址空間區域,那么該位址區域就完全不能被定址,CPU將拒絕存取該位址區域。
每個(gè)描述符佔用8個(gè)位元組,其中含有所描述段在線(xiàn)性位址空間中的起始位址(基址) ,段的長(cháng)度、段的類(lèi)型(例如代碼段和資料段) 、段的特權級別和其他一些資訊。一個(gè)段可定義的最大長(cháng)度是4GB。
保存描述符項的描述表有3種類(lèi)型,每種用於不同目的。全域描述符表GDT(Global Descriptor Table)是主要的基本描述符表,該表可被所有程式用於參照引用存取一個(gè)記憶段。中斷描述符表IDT(Interrupt Descriptor Table)保存有定義中斷或異常處理過(guò)程的段描述符。IDT表直接替代了8086系統中的中斷向量表。為了能在80X86保護模式下正常執行,我們必須為CPU定義一個(gè)GDT表和一個(gè)IDT表。最后一種類(lèi)型的表是區域描述符表LDT(Local
Descriptor Table)。該表應用於多工系統中,通常每個(gè)任務(wù)使用一個(gè)LDT表。作為對GDT表的擴充,每個(gè)LDT表為對應任務(wù)提供了更多的可用描述符項,因而也為每個(gè)任務(wù)提供了可定位記憶體空間的范圍。這些表可以保存在線(xiàn)性位址空間的任何地方。為了讓CUP能定位GDT表、IDT表和當前的LDT表,需要為CPU分別設置GDTR、IDTR和LDTR三個(gè)特殊寄存器。這些寄存器中將儲存對應表的32位元線(xiàn)性基底位址和表的限長(cháng)位元組值。表限長(cháng)值是表的長(cháng)度值-1。
當CPU要定址一個(gè)段時(shí),就會(huì )使用16位的段寄存器中的選擇符來(lái)定位一個(gè)段描述符。在80X86 CPU中,段寄存器中的值右移3位即是描述符表中一個(gè)描述符的索引值。13位元的索引值最多可定位8192(0- -8191)個(gè)的描述符項。選擇符中位元2 (TI)用來(lái)指定使用哪個(gè)表。若該位是0則選擇符指定的是GDT表中的描述符,否則是LDT表中的描述符。
每個(gè)程式都可有若干個(gè)記憶體段組成。程式的邏輯位址(或稱(chēng)為虛擬位址)即是用於定址這些段和段中具體位址位置。在Linux 0.12中,程式邏輯位址到線(xiàn)性位址的變換過(guò)程使用了CPU的全域段描述符表GDT和區域段描述符表LDT 。由GDT映射的位址空間稱(chēng)為全域位址空間,由LDT映射的位址空間則稱(chēng)為區域位址空間,而這兩者構成了虛擬位址的空問(wèn)。具體的使用方式見(jiàn)圖5-8所示。
圖中畫(huà)出了具有兩個(gè)任務(wù)時(shí)情況??梢钥闯?,每個(gè)任務(wù)的區域描述符表LDT本身也是由GDT中描述符定義的一個(gè)記憶體段,在該段中存放著(zhù)對應任務(wù)的代碼段和資料段描述符,因此LDT段很短,其段限長(cháng)通常只要大於24位元組即可。同樣,每個(gè)任務(wù)的任務(wù)狀態(tài)段TSS也是由GDT中描述符定義的一個(gè)記憶體段,其段限長(cháng)也只要滿(mǎn)足能夠存放一個(gè)TSS資料結構就夠了。
對於中斷描述符表idt,它保存在內核代碼段中。由於在Linux 0.12內核中,內核和各任務(wù)的代碼段和資料盡都分別被映射到線(xiàn)性位址空間中相同基址處,且段限長(cháng)也一樣,因此內核的代碼段和資料段是重疊的,各任務(wù)的代碼段和資料段分別也是重疊的,參見(jiàn)圖5-10 圖5-11所示。任務(wù)狀態(tài)段TSS(Task State Segment)用於在任務(wù)切換時(shí)CPU自動(dòng)保存或恢復相關(guān)任務(wù)的當前執行上下文(CPU當前狀態(tài)) 。例如對於切換出的任務(wù),CPU就把其暫存器等資訊保存在該任務(wù)的TSS段中,同時(shí)CPU 目新切換進(jìn)任務(wù)的TSS段中的資訊來(lái)設置各寄存器,以恢復該任務(wù)的執行環(huán)境,參見(jiàn)圖4-37所示。在Linux 0.12中,每個(gè)任務(wù)的TSS段內容被保存 在該任務(wù)的任務(wù)資料結構中。另外,Linux 0.12內核中沒(méi)有使用到系統段描述符。
5.3.4 記憶體分頁(yè)管理
若採用了分頁(yè)機制,則此時(shí)線(xiàn)性位址只是一個(gè)中間結果,還需要使用分頁(yè)機制進(jìn)行變換,再最終映射到實(shí)際實(shí)體記憶體位址上。與分段機制類(lèi)似,分頁(yè)機制允許我們重新定向(變換) 每次記憶體引用,以適應我們的特殊要求。使用分頁(yè)機制最普遍的場(chǎng)合是當然記憶體實(shí)際上被分成很多凌亂的區塊時(shí),它可以建立一個(gè)大而連續的記憶體空間映射,好讓程式不用操心和管理這些分散的記憶體區塊。分頁(yè)機制增強了分段機制的效能。另外,頁(yè)位址變換建立在段變換基礎之上,任何分頁(yè)機制的保護措施不會(huì )取代段變換的保護措施而只是進(jìn)行更進(jìn)一步的檢查操作。
記憶體分頁(yè)管理機制的基本原理是將CPU整個(gè)線(xiàn)性記憶體區域劃分成4096位元組為1頁(yè)的記憶體頁(yè)面。程式申請使用記憶體時(shí),系統就以記憶體頁(yè)為單位進(jìn)行分配。記憶體分頁(yè)機制的實(shí)現方式與分段機制很相似,但並不如分段機制那麼完善。因為分頁(yè)機制是在分段機制之上實(shí)現的,所以其結果是對系統記憶體具有非常靈活的控制權,并且在分段機制的記憶體保護上更增加了分頁(yè)保護機制。為了在80X86保護模式下使用分頁(yè)機制,需要把控制寄存器CR0的最高Bit位(位31)置位。
在使用這種記憶體分頁(yè)管理方法時(shí),每個(gè)執行中的行程(任務(wù))可以使用此實(shí)際記憶體容量大得多的連續位址空間。為了在使用分頁(yè)機制的條件下把線(xiàn)性位址映射到容量相對很小的實(shí)體記憶體空間上,80386使用了頁(yè)目錄表和頁(yè)表。頁(yè)目錄表項與頁(yè)表項格式基本相同,都佔用4個(gè)位元組,並且每個(gè)頁(yè)目錄表或頁(yè)表必須只能包含1024個(gè)頁(yè)表項。因此一個(gè)頁(yè)目錄表或一個(gè)頁(yè)表分別共佔用l頁(yè)記憶體。頁(yè)目錄項和頁(yè)表項的小區別在於頁(yè)表項有個(gè)已寫(xiě)D (Dirty),而頁(yè)目錄則沒(méi)有。
線(xiàn)性位址到實(shí)體位址的變換過(guò)程見(jiàn)圖5-9所示。圖中控制寄存器CR3保存著(zhù)是當前頁(yè)目錄表在實(shí)體記憶體中的基底位址(因此CR3也被稱(chēng)為頁(yè)目錄基底位址暫存器PDBR)o。32 元的線(xiàn)性位址被分成三個(gè)部分,分別用來(lái)在頁(yè)目錄表和頁(yè)表中定位對應的頁(yè)目錄項和頁(yè)表項以及在對應的實(shí)體記憶體頁(yè)面中指定頁(yè)面內的偏移位置。因為l 個(gè)頁(yè)表可有1024項,因此一個(gè)頁(yè)表最多可以映射1024*4KB = 4MB記憶體; 因為一個(gè)頁(yè)目錄表最多有1024項,對應1024個(gè)二級頁(yè)表,因此一個(gè)頁(yè)目錄表最多可以映射1024*4MB = 4GB容量的記憶體。即一個(gè)頁(yè)目錄表就可以映射整個(gè)線(xiàn)性位址空間范圍。
由於Linux 0.1x系統中內核和所有任務(wù)都共用同一個(gè)頁(yè)目錄表,使得任何時(shí)刻處理器線(xiàn)性位址空間到實(shí)體位址空間的映射函數都一樣。因此為了讓內核和所有任務(wù)都不互相重疊和干擾 ,它們都必須從虛擬位址空間映射到線(xiàn)性位址空間的不同位置,即佔用不同的線(xiàn)性位址空間范圍。
對於Intel 80386系統,其CPU可以提供多達4G的線(xiàn)性位址空間。一個(gè)任務(wù)的虛擬位址需要首先透過(guò)其區域段描述符變換為CPU整個(gè)線(xiàn)性位址空間中的位址,然后再使用頁(yè)目錄表PDT (一級頁(yè)表)和頁(yè)表PT (二級頁(yè)表)映射到實(shí)際實(shí)體位址頁(yè)上。為了使用實(shí)際實(shí)體記憶體,每個(gè)行程的線(xiàn)性位址透過(guò)二級記憶體頁(yè)表動(dòng)態(tài)地映射到主記憶體區域的不同實(shí)體記憶體頁(yè)上。由於Linux 0.12中把每個(gè)行程最大可用虛擬記憶體空間定義為64MB,因此每個(gè)行程的邏輯位址透過(guò)加上(任務(wù)號)*64MB,即可轉換為線(xiàn)性空間中的位址。不過(guò)在注釋中,在不至於搞混的情況下我們有時(shí)將行程中的此類(lèi)位址簡(jiǎn)單地稱(chēng)為邏輯位址或線(xiàn)性位址。
對於Linux 0.12系統,內核設置全域描述符表GDT中的段描述符項數最大為256,其中2項空間、2項系統使用,每個(gè)行程使用兩項。因此,此時(shí)系統可以最多容納(256-4)/2 = 126個(gè) 任務(wù),並且虛擬位址范圍是((256-4)/2)*64MB約等於8G。但0.12內核中人工定義最大任務(wù)數NR_TASKS = 64個(gè),每個(gè)任務(wù)邏輯位址范圍是64M,並且各個(gè)任務(wù)在線(xiàn)性位址空間中的起始位置是(任務(wù)號)*64MB。因此全部任務(wù)所使用的線(xiàn)性位址空間范圍是64MB*64 = 4G,見(jiàn)圖5-10所示。圖中示出了當系統具有4個(gè)任務(wù)時(shí)的情況。內核代碼段和資料段被映射到線(xiàn)性位址空間的開(kāi)始16MB部分,並且代碼和資料段都映射到同一個(gè)區域,完全互相重疊。而第1個(gè)任務(wù)(任務(wù)0)是由內核“人工”啟動(dòng)執行的,其代碼和資料包含在內核代碼和資料中,因此該任務(wù)所佔用的線(xiàn)性位址空間范圍比較特殊。任務(wù)0的代碼段和資料段的長(cháng)度是從線(xiàn)性位址0開(kāi)始的640KB范圍,其代碼和資料段也完全重疊,并且與內核代碼段和資料段有重疊的部分。實(shí)際上,Linux 0.12中所有任務(wù)的指令空間I (Instruction)和資料空間D (Data)都合用一塊記憶體,即一個(gè)行程的所有代碼、資料和堆疊部分都處於同一記憶體段中,也即是I&D不分離的一種使用方式。
任務(wù)1的線(xiàn)性位址空間范圍也只有從64MB開(kāi)始的640KB長(cháng)度。它們之間的詳細對應關(guān)系見(jiàn)后面說(shuō)明。任務(wù)2和任務(wù)3分別被映射線(xiàn)性位址128MB和192MB開(kāi)始的地方,並月它們的邏輯位址范圍均是64MB 。由於4G位址空間范圍正好是CPU的線(xiàn)性位址空間范圍和可定址的最大實(shí)體位址空間范圍,而且在把任務(wù)0和任務(wù)l(shuí)的邏輯位址范圍看作64MB時(shí),系統中同時(shí)可有任務(wù)的邏輯位址范圍總和也是4GB,因此在0.12內核中比較容易混淆三種位址概念。
如果也按照線(xiàn)性空間中任務(wù)的排列順序排列虛擬空間中的任務(wù),那麼我們可以有圖5-11所示的系統I 同時(shí)可擁有所有任務(wù)在虛擬位址空間中的示意圖,所佔用虛擬空間范圍也是4GB (中沒(méi)有考慮內核代碼和資料在虛擬空間中所佔用的范圍。另外,在圖中對於行程2和行程3還分別給出了各自邏輯空間中代碼段和資料段(包括資料和堆棧內容)的位置示意圖。
我們還需要注意的,是行程邏輯位址空間中代碼段(Code Section)和資料段(DataSection)的概念與CPU分段機制中的代碼段和資料段不是同一個(gè)概念。CPU分段機制中段的概念確定了在線(xiàn)性位址空間中一個(gè)段的用途以及被執行或存取的約束和限制,每個(gè)段可以設置在4GB線(xiàn)性位址空間中的任何地方,它們可以相互獨立也可以完全重疊或部分重疊。而行程在其邏輯位址空間中的代碼段和資料段則是指由編譯器在編譯程序和作業(yè)系統在載入程式時(shí)規定的在行程邏輯空間中順序排列的代碼區域、初始化和未初始化的資料區域以及堆棧區域。行程邏輯位址空間中代碼段和資料段等結構形式見(jiàn)圖所示。有關(guān)邏輯位址空間的說(shuō)明請參閱記憶體管理一章內容。
5.3.5CPU多工和保護方式
Intel 80X86 CPU是分為4個(gè)保護級別,0級別具有最高優(yōu)先順序,而3級別優(yōu)先順序最低。在Linux 0.12作業(yè)系統里是使用了CPU的0和3兩個(gè)保護級。內核代碼本身會(huì )由系統中的所有任務(wù)共用。而每個(gè)任務(wù)則都有自己的代碼和資料區,這兩個(gè)區域保存於區域位址空間,因 次系統中的其他任務(wù)是看不見(jiàn)的(不能存取的) 。而內核代碼和資料是由所有任務(wù)共用的,因此它保存在全域位址空間中。圖5-13出示了這種結構的示意圖。圖中同心圓代表CPU的保護級別 (保護層) ,這裡僅使用了CPU的0級和3級。而徑向射線(xiàn)則用來(lái)區分系統中的各個(gè)任務(wù)。每條徑向射線(xiàn)指出了各任務(wù)的邊界。除了每個(gè)任務(wù)虛擬位址空問(wèn)的全域位址區域,任務(wù)1中的位址與任務(wù)2中相同位址處是無(wú)關(guān)的。
當一個(gè)任務(wù)(行程)執行系統呼叫而陷入內核代碼中執行時(shí),我們就稱(chēng)行程處於內核執行態(tài)(或簡(jiǎn)稱(chēng)為內核態(tài)) 。此時(shí)處理器處於特權級最高的(0級)內核代碼中執行。當行程處於內核態(tài)時(shí),執行的內核代碼會(huì )使用當前行程的內核堆棧。每個(gè)行程都有自己的內核堆棧。當行程在執行用戶(hù)自己的代碼時(shí),則稱(chēng)其處於用戶(hù)執行態(tài)(用戶(hù)態(tài)) 即此時(shí)處理器在特權級最低的(3級)用戶(hù)代碼中執行。當正在執行用戶(hù)程式突然被中斷程式中斷時(shí),此時(shí)用戶(hù)程式也可以象征性地稱(chēng)為處於行程的內核態(tài)。因為中斷處理程式將使用當前行程的內核堆棧。這與處於內核態(tài)的行程的狀態(tài)有些類(lèi)似。行程的內核態(tài)和用戶(hù)態(tài)將在后面有關(guān)行程執行狀態(tài)一節中作更詳細說(shuō)明。
5.3.6 虛擬位址、線(xiàn)性位址和實(shí)體位址之間的關(guān)系
前面我們根據記憶體分段和分頁(yè)機制詳細說(shuō)明了CPU的記憶體管理方式?,F在我們以L(fǎng)inux 0.12 系統為例,詳細說(shuō)明內核代碼和資料以及各任務(wù)的代碼和資料在虛擬位址空間、線(xiàn)性位址空間和實(shí)體位址空間中的對應關(guān)系。由於任務(wù)0和任務(wù)l(shuí)的生成或建立過(guò)程比較特殊,我們將對它們分別進(jìn)行描述。
內核代碼和資料的位址
對於Linux 0.12內核代碼和資料來(lái)說(shuō),在head.s程式的初始化操作中已經(jīng)把內核代碼段和資料段都設置成為長(cháng)度為16MB的段。在線(xiàn)性位址空間中這兩個(gè)段的范圍重疊,部是從線(xiàn)性位址0開(kāi)始到位址0xFFFFFF共16MB地址范圍。在該范圍中含有內核所有的代碼、內核段表(GDT、IDT、TSS) 、頁(yè)目錄表和內核的二級頁(yè)表、內核區域資料以及內核臨時(shí)堆棧(將被用作第l個(gè)任務(wù)即任務(wù)0的用戶(hù)堆棧)。其頁(yè)目錄和二級頁(yè)表已設置成把0- -16MB的線(xiàn)性位址空間一一對應到實(shí)體位址上,佔用了4個(gè)目錄項,即4個(gè)二級頁(yè)表。因此對於內核代碼或資料的位址來(lái)說(shuō),我們可以直接把它們看作是實(shí)體記憶體中的位址。此時(shí)內核的虛擬位址空間、線(xiàn)性位址空間和實(shí)體位址空間三者之間的關(guān)系可用圖5-14來(lái)表示。

因此,預設情況下Linux 0.12內核最多可管理16MB的實(shí)體記憶體,共有4096個(gè)實(shí)體頁(yè)面(頁(yè)框) ,每個(gè)頁(yè)面4KB。透過(guò)上述分析可以看出:①內核代碼段和資料段區域在線(xiàn)性位址空間和實(shí)體位址空間中是一樣的。這樣設置可以大大簡(jiǎn)化內核的初始化操作。②GDT和IDT在內核資料段中,因此它們的線(xiàn)性位址也同樣等於它們的實(shí)體位址。在真實(shí)模式下的setup.s程式初始化操作中,我們曾經(jīng)設置過(guò)臨時(shí)的GDT和IDT,這是進(jìn)入保護模式之前必須設置的。由于這兩個(gè)表當時(shí)處於實(shí)體記憶體大約0x90200處,而進(jìn)入保護模式后內核系統模組處於實(shí)體記憶體。開(kāi)始位置,并且0x90200處的空間將被挪作他用(用於高速緩沖),因此在進(jìn)入保護模式后,在執行的第l個(gè)程式head.s中我們需要重新設置這兩個(gè)表。即設置GDTR和IDT指向新的GDT和IDT,描述符也需要重新載入。但由於開(kāi)啟分頁(yè)機制時(shí)這兩 個(gè)表的位置沒(méi)有變動(dòng),因此無(wú)須再重新建立或移動(dòng)表位置。③除任務(wù)0以外,所有其他他任務(wù)所需要的實(shí)體記憶體頁(yè)面與線(xiàn)性位址中的不同或部分不同,因此內核需要動(dòng)態(tài)地在主記憶體區中為它們作映射操作,動(dòng)態(tài)地建立頁(yè)目錄項和頁(yè)表項。雖然任務(wù)1的代碼和資料也在內核中,但由於他需要另行分配獲得記憶體,因此也需要自己的映射表項。
雖然Linux 0.12可管理16MB實(shí)體記憶體,但是系統中並不是一定要有這些實(shí)體記憶體。機器中只要有4MB(甚至2MB)實(shí)體記憶體就完全可以執行Linux 0.12系統了。若機器只有4MB實(shí)體記憶體,那麼此時(shí)內核4MB- -1 6MB位址范圍就會(huì )映射到不存在的實(shí)體記憶體位址上。但這並不妨礙系統的執行。因為在初始化時(shí)內核記憶體管理程式會(huì )知道機器中所含實(shí)體記憶體量的確切大小,因而不會(huì )讓CPU分頁(yè)機制把線(xiàn)性位址頁(yè)面映射到不存在的4MB- -16MB中去。內核中這樣的預設設置主要是為了便於系統實(shí)體記憶體的擴充,實(shí)際並不會(huì )用到不存在的實(shí)體記憶體區域。如果系統有多於16MB的實(shí)體記憶體,由於在init/main.c程式中初始化時(shí)限制了對16MB以上記憶體的使用,並且這裡內核也僅映射了0- -16MB的記憶體范圍,因此在16MB之上的實(shí)體記憶體將不會(huì )用到。
透過(guò)在這裡為內核增加一些頁(yè)表,並且對init/main.c程式稍作修改,我們可以對此限制進(jìn)行擴充。任 在系統中有32MB實(shí)體記憶體的情況下,我們就需要為內核代碼和資料段建立8個(gè)二級頁(yè)表項來(lái)把32MB的線(xiàn)性位址范圍映射到實(shí)體記憶體上。
任務(wù)0的位址對應關(guān)系
任務(wù)0是系統中一個(gè)人工啟動(dòng)的第一個(gè)任務(wù)。它的代碼段和資料段長(cháng)度被設置為640KB。該任務(wù)的代碼和資料直接包含在內核代碼和資料中,是從線(xiàn)性位址0開(kāi)始的640KB內容,因此可以它直接使用內核代碼已經(jīng)設置好的頁(yè)目錄和頁(yè)表進(jìn)行分頁(yè)位址變換。同樣它的代碼和資料段在線(xiàn)性位址空間中也是重疊的。對應的仟務(wù)狀態(tài)段TSS0也是手工預設置好的,並且位於任務(wù)0資料結構資訊中,參見(jiàn)include/linux/sched.h 第156行開(kāi)始的資料。TSS0段位於內核sched.c程式的代碼中,長(cháng)度為104位元組,具體位置可參見(jiàn)圖5-24中“任務(wù)0結構資訊”項所示。三個(gè)位址空中的映射對應關(guān)系見(jiàn)圖5-15所示。
由於任務(wù)0直接被包含內核代碼中,因此不需要為其再另外分配記憶體頁(yè)。它執行時(shí)所需要的內核態(tài)堆棧和用戶(hù)態(tài)堆??臻g也都在內核代碼區中,並且由於在內核初始化時(shí)(head.s )這些內核頁(yè)面在頁(yè)表項中的屬性都已經(jīng)被設置成了 0b111,即對應頁(yè)面用戶(hù)可讀寫(xiě)並且存在,因此用戶(hù)堆棧user_stack[ ]空間雖然在內核空間中,但任務(wù)0仍然能對其進(jìn)行讀寫(xiě)操作。
任務(wù)1的位址對應關(guān)系
與任務(wù)0類(lèi)似,任務(wù)1也是一個(gè)特殊的任務(wù)。它的代碼也在內核代碼區域中。與任務(wù)0不同的是在線(xiàn)性位址空間中,系統在使用fork ( )建立任務(wù)l(shuí) (init行程)時(shí)為存放任務(wù)1的二級頁(yè)表而在主記憶體區申請了一頁(yè)記憶體來(lái)存放,並復制了父行程(任務(wù)0)的頁(yè)目錄和二級頁(yè)表項。因此任務(wù)1有自己的頁(yè)目錄和頁(yè)表表項,它把任務(wù)1 佔用的線(xiàn)性空間范圍64MB- -128MB(實(shí)際上是64MB- -64MB+640KB)也同樣映射到了實(shí)體位址0- -640KB處。此時(shí)任務(wù)l(shuí)的長(cháng)度也是640KB,並且其代碼段和資料段相重疊,只佔用一個(gè)頁(yè)目錄項和一個(gè)二級頁(yè)表。另外,系統還會(huì )為任務(wù)l(shuí) 在主記憶體區域中申請一頁(yè)記憶體用來(lái)存放它的任務(wù)資料結構和用作任務(wù)l(shuí) 核堆??臻g。任務(wù)資料結構(也稱(chēng)行程控制塊PCB)資訊中包括任務(wù)l(shuí)的TSS段結構資訊。見(jiàn)圖5-16所示。

任務(wù)l(shuí)的用戶(hù)態(tài)堆??臻g將直接共用使用處於內核代碼和資料區域(線(xiàn)性位址0- -640KB)中任務(wù)0的用戶(hù)態(tài)堆??臻guser_stack[ ](參見(jiàn)kernel/sched.c第82- -87行) ,因此這個(gè)堆棧要在任務(wù)1實(shí)際使用之前保持“干淨”,以確保被復制用於任務(wù)l(shuí)的堆棧不含有無(wú)用資料。在剛開(kāi)始建立任務(wù)l(shuí)時(shí),任務(wù)0用戶(hù)態(tài)堆棧user_stack[ ]與任務(wù)1共用使用,但當任務(wù)l(shuí)開(kāi)始執行時(shí),由於任務(wù)1映射到user stack[ ]處的頁(yè)表項被設置成唯讀,使得任務(wù)l(shuí)在執行堆棧操作時(shí)將會(huì )引起寫(xiě)頁(yè)面異常,從而由內核另行分配主記憶體區頁(yè)面作為堆??臻g使用。
其他任務(wù)的位址對應關(guān)系
對於被建立的從任務(wù)2開(kāi)始的其他任務(wù),它們的父行程都是init(任務(wù)l(shuí))行程。我們已經(jīng)知道,在Linux 0.1 2系統中共可以有64個(gè)行程同時(shí)存在,下面我們以任務(wù)2為例來(lái)說(shuō)明其他任何任務(wù)對位址空間的使用情況。
從任務(wù)2開(kāi)始,如果任務(wù)號以nr來(lái)表示,那麼任務(wù)nr在線(xiàn)性位址空間中的起始位置將被設定在nr*64M 莛。例如任務(wù)2的開(kāi)始位置= nr*64MB = 2*64MB = 128MB。任務(wù)代碼段和資料段的最大長(cháng)度被設置為64MB,因此任務(wù)2佔有的線(xiàn)性位址空間范圍是128MB- -192MB,共佔用64MB/4MB = 16個(gè)頁(yè)目錄項。虛擬空間中任務(wù)代碼段和資料段都被映射到線(xiàn)性位址空間相同的范圍,因此它們也完全重疊。圖5-17 顯示出了任務(wù)2的代碼段和資料段在三種位址空間中的對應關(guān)系。
在任務(wù)2被建立出來(lái)之后,將在其中執行execve( )函數來(lái)執行shell程式。當內核透過(guò)復制任務(wù)1剛建立任務(wù)2時(shí),除了佔用線(xiàn)性位址空間范圍不同外(128MB- -128MB+640KB),此時(shí)任務(wù)2的代碼和資料在三種位址空間中的關(guān)系與任務(wù)1的類(lèi)似。當任務(wù)2的代碼(init ( ))呼叫execve( )系統呼叫開(kāi)始載入並執行shell程式時(shí),該系統呼叫會(huì )叫釋放掉從任務(wù)l(shuí)復制的頁(yè)目錄和頁(yè)表表項及相應記憶體頁(yè)面,然后為新的執行程式shell重新設置相關(guān)頁(yè)目錄和頁(yè)表表項。圖5-17給出的是任務(wù)2中開(kāi)始執行程式時(shí)的情況,即任務(wù)2原先復制任務(wù)的代碼和資料被shell程式的代碼段和資料段替換后的情況。圖中顯示出已經(jīng)映射了一頁(yè)實(shí)體記憶體頁(yè)面的情況。這十 ;注意,在執行execve()函數時(shí),系統雖然在線(xiàn)性位址空間為任務(wù)2分配了64MB的空間范圍,但是內核並不會(huì )立刻為其分配和映射實(shí)體記憶體頁(yè)面。只有當任務(wù)2開(kāi)始執行時(shí)由於發(fā)生缺頁(yè)而引起異常時(shí)才會(huì )由記憶體管理程式為其在主記憶體區中分配並映射一頁(yè)實(shí)體記憶體到其線(xiàn)性位址空間中。這種分配和映射實(shí)體記憶億體頁(yè)面的方法稱(chēng)為需求載入(Load ondemand)。參見(jiàn)記憶體管理一章中的相關(guān)描述。
從linux內核0.99版以后,對記憶體空間的使用方式發(fā)生了變化。每個(gè)行程可以單獨享用整個(gè)4G的位址空間范圍。如果我們能理解本節說(shuō)描述的記憶體管理概念,那麼對於現在所使用的Lin 2.x內核中所使用的記憶體管理原理也能立刻明白。由於篇幅所限,這裡對此再說(shuō)明。
5.3.7 用戶(hù)申請記憶體的動(dòng)態(tài)分配
當用戶(hù)應用程式使用C函數庫中的記憶體分配函數malloc( )申請記憶體時(shí),這些動(dòng)態(tài)申請的記憶體容量或大小均臣 層次的C程式庫函數malloc( )來(lái)進(jìn)行管理,內核本身並不會(huì )插手管理。因為內核已經(jīng)為每個(gè)行程(除了任務(wù)0和1,它們與內核代碼一起常駐記憶體中)在CPU的4G線(xiàn)性位址空間中分配了64MB的空間,所以只要行程執行時(shí)定址的范圍生它的64MB范圍內,內核也同樣會(huì )透過(guò)記憶體缺頁(yè)管理機制自動(dòng)為定址對應頁(yè)面分配實(shí)體記憶體頁(yè)面並進(jìn)行映射操作。但是內核會(huì )為行程使用的代碼和資料空間維護一個(gè)當前位置值brk,這個(gè)值
保存在每個(gè)行程的資料結構中。它指出了行程代碼和資料(包括動(dòng)態(tài)分配的資料空間)在行程位址空間中的末端位置 。當malloc( )函數為程式分配記憶體時(shí),它會(huì )透過(guò)系統呼叫brk( )把程式要求新增的空間長(cháng)度通知內核,內核代碼從而可以根據malloc ( )所提供的資訊來(lái)更新brk 的值,但並此時(shí)並不為新申請的空間映射實(shí)體記憶體頁(yè)面。只有當程式定址到某個(gè)不存在對應實(shí)體頁(yè)面的位址時(shí),內核才會(huì )進(jìn)行相關(guān)實(shí)體記憶體頁(yè)面的映射操作。
若行程代碼定址的某個(gè)資料所在釅 面不存在,並且該頁(yè)面所處位置屬於行程堆范圍,即不屬於其執行檔映射檔蚤 抅記憶體范圍中,那麼CPU就會(huì )產(chǎn)生一個(gè)缺頁(yè)異常,並在異常處理程式中為 宅的頁(yè)面分配並映射一頁(yè)實(shí)體記憶體頁(yè)面。至於用戶(hù)程式此次申請記憶體的位 徂長(cháng)度數量和在對應實(shí)體頁(yè)面中的具體位置,則均由C程式庫中記憶體分配區 malloc()負責管理。內核以頁(yè)面為單位分配和映射實(shí)體記憶體,該函數則具體 家用戶(hù)程式使用了一頁(yè)記憶體的多少位元組‘剩余的容量將保留給程式再申請意體時(shí)使用。
當用戶(hù)使用記憶體釋放函數free ( ) 動(dòng)態(tài)釋放已申請的記憶體區塊時(shí),C庫中約記憶體管理函數就會(huì )把所釋放的記憶體區塊標記為空間,以備程式再次申請記憶體時(shí)使用。在這個(gè)過(guò)程中內核為該行程所分配的這個(gè)實(shí)體頁(yè)面並不會(huì )被釋放掉。只有當行程最終結束時(shí)內核才會(huì )全面收回已分配和映射到該行程位址空間范圍的所有實(shí)體記憶體頁(yè)面。
有關(guān)程式庫函數malloc ( )和free ( ) 的具體代碼實(shí)現請參見(jiàn)內核庫中的lib/malloc.c程式。
5.4 中斷機制
本節介紹中斷機制基本原理和相關(guān)的可程式化控制器硬體邏輯以及Linux系統中使用中斷的方法。有關(guān)可程式化控制器的具體程式設計方法請參見(jiàn)下一章setup.s程式后的說(shuō)明。
5.4.1中斷操作原理
微型電腦系統通常包括輸入輸出裝置。處理器向這些裝置提供服務(wù)的一種力法是使用輪詢(xún)方式。在這種方法中處理器順序地查詢(xún)系統中的每個(gè)裝置,“詢(xún)問(wèn)”它們是否需要服務(wù)。這種方法的優(yōu)點(diǎn)是軟體程式設計簡(jiǎn)單,但缺點(diǎn)是太耗處理器資源,影響系統效能。向裝置提供服務(wù)的另一種方法是在裝置需要服務(wù)時(shí)自己向處理器提出請求。處理器也只有在裝置提出請求時(shí)才為其提供服務(wù)。
當裝置向處理器提出服務(wù)請求時(shí),處理器會(huì )在執行完當前的一條指令后立刻應答裝置的請求,並轉而執行該裝置的相關(guān)服務(wù)程式。當服務(wù)程式執行完成后,處理器會(huì )接著(zhù)去做剛才被中斷的程式。這種處理方式就叫做中斷(Interrupt)方法,而裝置向處理器發(fā)出的服務(wù)請求則稱(chēng)為中斷請求(IRQ - InterruptRequest) 。處理器回應請求而執行的裝置相關(guān)程式則被稱(chēng)為中斷服務(wù)常式或中斷服務(wù)過(guò)程(ISR - Interrupt Service Routine)。
可程式化控制器(PIC - Programmable Interrupt Controller)是微機系統中管理裝置中斷請求的管理者。它透過(guò)連接到裝置的中斷請求接腳接受裝置發(fā)出的終端服務(wù)請求信號。當裝置啟動(dòng)其中斷請求IRQ信號時(shí),PIC立刻會(huì )檢測到。在同時(shí)收到幾個(gè)裝置的中斷服務(wù)請求的情況下,PIC會(huì )對它們進(jìn)行優(yōu)先順序比較並選出最高優(yōu)先順序的中斷請求進(jìn)行處理。如果此時(shí)處理器正在執行一個(gè)裝置的中斷服務(wù)過(guò)程,那麼PIC還需要把選出的中斷請求與正在處理的中斷請求的優(yōu)先順序進(jìn)行比較,並基於該比較結果來(lái)確定是否向處理器發(fā)出一個(gè)中斷信號。當PIC向處理器的INT接腳發(fā)出一個(gè)中斷訊號時(shí),處理器會(huì )立刻停下當時(shí)所做的事情並詢(xún)問(wèn)PIC需要執行哪個(gè)中斷服務(wù)請求。PIC則透過(guò)向資料匯流排發(fā)送出與中斷請求對應的中斷號來(lái)告知處理器要哪個(gè)中斷服務(wù)過(guò)程。處理器則根據讀取的中斷號透過(guò)查詢(xún)中斷向量表(或32位元保護模式下的中斷描述符表)取得相關(guān)裝置的中斷向量(即中斷服務(wù)程式的位址)並開(kāi)始執行中斷服務(wù)程式。當中斷服務(wù)程式執行結束,處理器就繼續執行中斷信號打斷的程式。
以上描述的是輸入輸出裝置的中斷服務(wù)處理過(guò)程。但是中斷方法並非一定與硬體相關(guān),它也可以用於軟體中。透過(guò)使用int指令並使用其運算元指明中斷號,就可以讓處理器去執行相應的中斷處理過(guò)程。PC/AT系列PC機共提供了對256個(gè)中斷的支援,其中大部分都用於軟體中斷或異常,異常是處理器在處理過(guò)程中檢測到錯誤而產(chǎn)生的中斷操作。只有下面及的一些中斷被用於裝置上。
5.4.2 80X86 PC機的中斷子系
在使用80X86組成的微機系統中採用8259A可程式化中斷控制器晶片。每個(gè)8259A晶片可以管理8個(gè)中斷源。透過(guò)多片串聯(lián)方式(cascade) ,8259A能構成最多管理64個(gè)中斷向量的系統。PC/AT系列兼容機中,使用了兩片8259A晶片,共可管理15級中斷向量。其級連示意圖見(jiàn)圖5-18所示。其中從晶片的INT接腳連接到主晶片的IR2接腳,即8259A從晶片發(fā)出的中斷信號將作為8259A主晶片的IRQ2輸入信號。主8259A晶片的埠基底位址是0x20,從晶片是0xA0 。 IRQ9接腳的作用與PC/XT的IRQ2相同,即PC/AT機利用硬件電路把使用IRQ2的裝置的IRQ2接腳重新定向到了PIC的IRQ9接腳上,並利用BIOS中的軟體把IRQ9的中斷int 71 重新定向到了IRQ2的中斷int 0x0A的中斷處理過(guò)程。這樣一來(lái)可使得任何億 IRQ2的PC/XT的8位設配卡在PC/AT機下面仍然能正常使用。做到了PC機系列的向下相容性。
在匯流排控制器控制下,8259A晶片可以處于程式設計狀態(tài)和操作狀態(tài),程式設計伏態(tài)是CPU使用IN或OUT指令對82 59A晶片進(jìn)行初始化程式設計的狀態(tài)。一旦完成了初始化程式設計,晶片即進(jìn)入操作狀態(tài),此時(shí)晶片即可隨時(shí)回應外部裝置提出的中斷請求(IRQ0 - IRQ15),同時(shí)系統還可以使用操作命令字隨時(shí)修改其中斷處理方式。透過(guò)中斷判優(yōu)選,晶片將選中當前最高優(yōu)先順序的中斷清求作為中斷服務(wù)對象,並透過(guò)CPU接腳INT通知CPU外中斷請求的到來(lái),CPU回應后,晶片從資料匯流排D7-D0將程式設計設定的當前服務(wù)對象的中斷號送出,CPU由此獲取對應的中斷向量值,並執行中斷服務(wù)程式。
5.4.3 中斷向量表
上節已指出CPU是根據中斷號獲取中斷向量值,即對應中斷服務(wù)程式的入口位址直。因此為了讓CPU由中斷號查找到對應得中斷向量,就需要在記憶體中建立一張查詢(xún)表,即中斷向量表(在32位元保護模式下該表稱(chēng)為中斷描述符表,見(jiàn)下面說(shuō)明)。80X86微機支持256個(gè)中斷,對應每個(gè)中斷需要安排一個(gè)中斷服務(wù)程式。在80X86真實(shí)模式執行方式下,每個(gè)中斷向量由4個(gè)位元組組成。這4個(gè)立元組指明了一個(gè)中斷服務(wù)程式的段值和段內偏移值。因此整個(gè)向量表的長(cháng)度為1024位元組。當80X86微機啟動(dòng)時(shí),ROM BIOS中的程式會(huì )在實(shí)體記
憶體開(kāi)冶位址0x0000 : 0x0000處初始化並設置中斷向量表,而各中斷的預設中斷服務(wù)程式則在BIOS中給出。由於中斷向量表中向量是按中斷號順序排列,因此給定一個(gè)中斷號N,那麼它對應的中斷向量在記憶體中的位置就是0x0000 : N*4,即對應的中斷服務(wù)程式入口位保存在實(shí)體記憶體0x0000 : N*4位置處。
在BIOS執行初始化操作時(shí),它設置了兩個(gè)8259A晶片支援的16個(gè)硬體中斷向量和BIOS提供的中斷號為0x10 - 0xlf 得中斷呼叫功能向量等。對於實(shí)際沒(méi)有使習的向量則填入臨時(shí)的啞中斷服務(wù)程式位址。以后在系統開(kāi)機載入作業(yè)系統時(shí)會(huì )根據實(shí)際需要修改某些中斷向量的值 列如,對於DOS作業(yè)系統,它會(huì )重新役置中斷0x20 – 0x2f的中斷向量值。而對於Linux系統,除了在剛開(kāi)始載入內核時(shí)需要用到BIOS提供的顯示和磁碟讀取操作中斷功能,在內核正常執行之前則會(huì )在setup.s程式中重新初始化8259 A晶片並且在head.s程式中重新設置一張中斷向量表(中斷描述符表)。完全拋棄BIOS所提供的中斷服務(wù)功能。
當Intel CPU執行在32位元保護模式下時(shí),需要使用中斷描述符表IDT (Interrupt Descriptor Table)來(lái)管理中斷或異常。IDT是Intel 8086- -80186CPU中使用的中斷向量表的直接替代物。其作用也類(lèi)似于中斷向量表,只是其中每個(gè)中斷描述符項中除了含有中斷服務(wù)程式位址以外,還包含有關(guān)特權級和描述符類(lèi)別等資訊。Linux作業(yè)系統工作於80X86的保護模式下,因此它使用中斷描述符表安來(lái)設置和保存各中斷的“向量”資訊。
5.4.4Linux內核的中斷處理
於于Linux內核來(lái)說(shuō),中斷信號通常分兩類(lèi) :硬體中斷和軟件中斷(異常)。每個(gè)中斷是由 0-255 之間的一個(gè)數字來(lái)標識。對於中斷int0- -int3l(0x00- -0xff),每個(gè)中斷的功能由Intel公司固定設定或保留用,屬於軟體中斷,但Intel公司稱(chēng)之為異常。因為這些中斷是在CPU執行指令時(shí)探測到異常晴況而引起的。通常還可分為故障(Fault)和陷阱(traps)兩類(lèi)。中斷
int32- -int255(0x20- -0xff)可以由用戶(hù)自己設定。所有中斷的分類(lèi)以及執行后CPU的動(dòng)作方式見(jiàn)表5-1所示。
主linux系統中,則將int32- -int47(0x20- -0x2f)對應於8259A中斷控制晶片發(fā)出的硬件中斷請求信號IRQ0- -IRQ15 (見(jiàn)表5-2所示) ,並把程式程式設計發(fā)出的系統呼叫(system call)中斷設置為int l28(0x80) 。系統呼叫中斷是用戶(hù)程式使用作業(yè)系統資源的唯一介面。
在系統初始化時(shí),內核在head.s程式中首先使用一個(gè)啞中斷向量(中斷描述符)對中斷描述符表(Interrupt Descriptor Tabl IDT)中所有256個(gè)描述符進(jìn)行了預設設置(boot/head.s,78)。這個(gè)啞中斷向量指向一個(gè)預設的“無(wú)中斷”處理過(guò)程(boot/head.s,150) 。當發(fā)生了一個(gè)中斷而又沒(méi)有重新設置過(guò)該中斷向量時(shí)就會(huì )顯示資訊“未知中斷(Unknown interrupt)”。這裡對所有256項都進(jìn)行設置可以有效防止出現一般保護性錯誤(A general protection fault) (異常13)。否則的話(huà),如果設置的IDT少於256項,那么在一個(gè)要求的中斷所指定的描述符耳大於設置的最大描述符項時(shí),CPU就回產(chǎn)生一個(gè)一般保護出錯(異常13) 。另外,如果硬件出現問(wèn)題而沒(méi)有把裝置的向量放到資料匯流排上,此時(shí)CPU通常會(huì )從資料匯流排上讀入全l (0xff)作為向量,因此會(huì )去讀取IDT表中的第256項,因此也會(huì )造成一般保護出錯。對於系統中需要使用的一些中斷,內核會(huì )在其繼續初始化的處理過(guò)程中(init/main.c) 重新設置這些中斷的中斷描述符項,讓它門(mén)指向對應的實(shí)際處理過(guò)程。通常,硬件異常中斷處理過(guò)程(int0 - -int 31)都在traps.c的初始化函數中進(jìn)行了重新設置(kernel/traps.c,第185行),而系統呼叫中斷 int l28則在調度程式初始化函數中進(jìn)行了重新設置(kernel/sched.c,第417行) 。
另外,在設置中斷描述符表IDT時(shí)Linux內使用了中斷門(mén)和陷阱門(mén)兩種描述符。它們之間的區別在於對標志寄存器EFLAGS中的中斷允許標志IF的影響。由中斷門(mén)描述符執行的中斷會(huì )重定IF標志,因此可以避免其他中斷干擾當前中斷的處埋,隨后的中斷結束指令iret會(huì )從堆棧上恢復IF標志的原值;而透過(guò)陷阱門(mén)執行的中斷則不會(huì )影響IF標志。請參閱第11章中對include/asm/system.h檔的說(shuō)明。
5.4.5 標志寄存器的中斷標志
為了避免競爭條件和中斷對臨界代碼區的干擾,在Linux 0.12內核代碼中許多地方使用了cli和sti指令。cli指令用來(lái)重定CPU標志寄存器中的中斷標志,使得系統在執行cli指令后不會(huì )回應外部中斷。sti指令用來(lái)設置標志寄存器中斷的標志,以允許CPU能識別並響應外部設置發(fā)出的中斷。當進(jìn)入可能引起競爭代碼區時(shí),內核中就會(huì )使用cli指令來(lái)關(guān)閉對外部中斷的回應,而在執行完整競爭代碼區時(shí)內核就會(huì )執行sti指令以重新允許CPU回應外部中斷。例如,在修改檔超級區塊(Super Block)的鎖定標志和任務(wù)進(jìn)入/退出等待佇列操作時(shí)都需要首先使用cli指令關(guān)閉CPU對外部中斷的回應,在操作完成之后再使用sti指令開(kāi)啟對外部中斷的回應。如果不使用cli、sti指令對,即在需要修改一個(gè)檔超級區塊時(shí)不使用cli來(lái)關(guān)閉對外部中斷回應,那麼在修改之前判斷出該超級區塊鎖定標志沒(méi)有置位元而想設置這個(gè)標志時(shí),若此時(shí)正好發(fā)生系統時(shí)鐘中斷而切換到其他任務(wù)去執行,並且碰巧其他任務(wù)也需要修改這個(gè)超級區塊,那麼此時(shí)這個(gè)其他任務(wù)會(huì )先設置超級區塊的鎖定標志并且對超級區塊進(jìn)行修改操作。當系統又切換回原來(lái)的任務(wù)時(shí),此時(shí)該任務(wù)不會(huì )再去判斷鎖定標志就會(huì )繼續執行設置
超級區塊的鎖定標志,從而造成兩個(gè)任務(wù)對臨界代碼區的同時(shí)多重操作,引起超級區塊數據的不一致性,嚴重時(shí)會(huì )導致內系統崩潰。
To be continued........