《Linux原理與結(jié)構(gòu)》課件第2章_第1頁
《Linux原理與結(jié)構(gòu)》課件第2章_第2頁
《Linux原理與結(jié)構(gòu)》課件第2章_第3頁
《Linux原理與結(jié)構(gòu)》課件第2章_第4頁
《Linux原理與結(jié)構(gòu)》課件第2章_第5頁
已閱讀5頁,還剩134頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)

文檔簡介

第二章平?臺?與?工?具2.1硬件平臺2.2Intel處理器體系結(jié)構(gòu)2.3GNUC語言2.4GNU匯編語言2.5GNU鏈接腳本2.6常用數(shù)據(jù)結(jié)構(gòu)如果不考慮虛擬機(jī)監(jiān)控器(VMM),操作系統(tǒng)內(nèi)核就是最底層的系統(tǒng)軟件。操作系統(tǒng)內(nèi)核直接運(yùn)行在計算機(jī)硬件平臺之上,其設(shè)計技術(shù)與實(shí)現(xiàn)方法都與硬件平臺有著十分密切的關(guān)系。離開了硬件平臺的支持,操作系統(tǒng)內(nèi)核的許多管理工作都難以開展。事實(shí)上,計算機(jī)硬件平臺中的許多功能也是專門為操作系統(tǒng)內(nèi)核設(shè)計的,只有操作系統(tǒng)內(nèi)核才會使用它們。要了解操作系統(tǒng)內(nèi)核的原理與結(jié)構(gòu),就必須了解計算機(jī)的硬件平臺。

在復(fù)雜的計算機(jī)硬件平臺中,最核心的是處理器,與內(nèi)核設(shè)計關(guān)系最密切的也是處理器。雖然Linux內(nèi)核可以運(yùn)行在多種處理器之上,但I(xiàn)ntel系列的處理器是Linux支持的第一種處理器,也是目前最常見的處理器,更是本書的討論基礎(chǔ)。

Linux內(nèi)核是用C和匯編語言寫成的,然而它所用的C語言經(jīng)過了GNU的擴(kuò)展,所用的匯編語言采用的是AT&T的格式。Linux內(nèi)核的實(shí)現(xiàn)充分利用了GNUC和AT&T匯編的擴(kuò)展特性,與這兩種語言的結(jié)合極為緊密。GNUC和AT&T格式的匯編是Linux的核心開發(fā)工具,也是理解Linux內(nèi)核源代碼的基礎(chǔ)。

另外,在Linux內(nèi)核的諸多數(shù)據(jù)結(jié)構(gòu)中,最常見的是鏈表和樹。鏈表和樹的實(shí)現(xiàn)方式很多,為了避免重復(fù),Linux設(shè)計了通用鏈表和紅黑樹。當(dāng)需要將某種結(jié)構(gòu)組織成鏈表或紅黑樹時,Linux就會在其中嵌入一個通用鏈表節(jié)點(diǎn)或紅黑樹節(jié)點(diǎn)。

操作系統(tǒng)所管理的計算機(jī)硬件平臺大致由CPU、內(nèi)存、外存和其它外部設(shè)備組成,它們之間通過總線連接在一起。圖2.1是一種抽象的計算機(jī)硬件平臺的組織結(jié)構(gòu)。2.1硬件平臺

圖2.1計算機(jī)硬件平臺的組織結(jié)構(gòu)處理器又叫CPU,是整個計算機(jī)系統(tǒng)的大腦,它負(fù)責(zé)執(zhí)行由指令構(gòu)成的程序,并通過程序的執(zhí)行來控制整個計算機(jī)系統(tǒng)。一個計算機(jī)系統(tǒng)中可以有一個或多個處理器,一個處理器中又可以有一個或多個核(Core)。為方便起見,可以將一個核看成一個獨(dú)立的處理器。一個以多核處理器為核心的計算機(jī)系統(tǒng)等價于一個多處理器(SMP)系統(tǒng)。

內(nèi)存是處理器執(zhí)行程序、加工數(shù)據(jù)的場所,是處理器可以直接訪問的存儲空間。內(nèi)存通常被抽象成一個字節(jié)數(shù)組,其中的每個字節(jié)都有一個地址。處理器可通過地址隨機(jī)地訪問內(nèi)存中的任意一個字節(jié)。為了加快內(nèi)存的訪問速度,計算機(jī)系統(tǒng)中通常都提供了一些高速緩存(Cache)。Cache通常由硬件管理。

I/O設(shè)備通常由I/O控制器和物理設(shè)備組成。處理器通過I/O控制器管理物理設(shè)備。對內(nèi)核來說,I/O控制器主要由控制與狀態(tài)寄存器(CSR)和數(shù)據(jù)寄存器組成。處理器通過讀CSR獲得設(shè)備的狀態(tài)、通過寫CSR來控制設(shè)備的動作、通過讀寫數(shù)據(jù)寄存器與I/O設(shè)備交換數(shù)據(jù)。因而,內(nèi)核通常將一個I/O設(shè)備抽象成一組寄存器,并給每個寄存器一個I/O地址。處理器通過I/O地址訪問所有的I/O寄存器。有些處理器還提供了I/O指令,專門用于訪問I/O寄存器,如Intel的in、out指令。現(xiàn)代計算機(jī)系統(tǒng)中的許多設(shè)備寄存器可被映射到物理地址空間中。此時,每個設(shè)備寄存器都有一個物理內(nèi)存地址,處理器可以像訪問物理內(nèi)存一樣訪問設(shè)備的寄存器。這種方式的I/O稱為內(nèi)存映射I/O(MMIO),它的使用更加方便,但會消耗物理地址。

在所有的I/O設(shè)備中,對系統(tǒng)影響最大的是外部存儲設(shè)備,如硬盤、光盤等。操作系統(tǒng)、應(yīng)用程序、數(shù)據(jù)文件等都存儲在外部存儲設(shè)備(如磁盤)上。為了便于管理,通常把外存抽象成一個數(shù)據(jù)塊的數(shù)組,每個數(shù)據(jù)塊都有一個序號。處理器可以通過序號隨機(jī)地讀、寫外存中的任何一個數(shù)據(jù)塊。對外存的操作以塊為單位,因此又稱外存為塊設(shè)備。對應(yīng)地,其它I/O設(shè)備稱為字符設(shè)備。

總線負(fù)責(zé)將處理器、內(nèi)存、I/O控制器等連接起來,組合成一個完整的計算機(jī)系統(tǒng)。常用的總線有ISA、PCI、PCI-E、AGP、ATA、SCSI等??偩€除負(fù)責(zé)計算機(jī)系統(tǒng)中各部件之間的通信之外,還負(fù)責(zé)檢測、枚舉連接在其上的設(shè)備,報告它們的信息。

在眾多的處理器中,最常見的是Intel處理器。Intel處理器是一個大家族,包括多個系列的產(chǎn)品,如80386、80486、Pentium、PentiumII、PentiumIII、Pentium4、Xeon、CoreTMDuo、CoreTMSolo等。若按處理器的體系結(jié)構(gòu)劃分,可將主流的Intel處理器分為兩大類,即IA-32和Intel64。其中,IA-32提供32位編程環(huán)境,Intel64提供64位編程環(huán)境。Intel64與IA-32是兼容的。2.2Intel處理器體系結(jié)構(gòu)

Intel處理器為操作系統(tǒng)內(nèi)核的設(shè)計提供了多種支持機(jī)制,包括操作模式、內(nèi)存管理機(jī)制、進(jìn)程管理機(jī)制、中斷處理機(jī)制、保護(hù)機(jī)制、專用寄存器和指令等。2.2.1處理器操作模式

定義操作模式的目的主要是為了兼容。在設(shè)計8086處理器時,Intel并沒有定義操作模式,此時的處理器使用20位的物理地址,最多可訪問1MB的物理內(nèi)存,也未對操作系統(tǒng)進(jìn)行任何保護(hù)。當(dāng)80386出現(xiàn)時,處理器開始使用32位的邏輯地址,而且提供了程序間的隔離與保護(hù),于是引入了保護(hù)模式以區(qū)分前期的實(shí)模式。為了在保護(hù)模式中能夠運(yùn)行實(shí)模式的程序,又引入了虛擬8086模式。當(dāng)Intel64出現(xiàn)之后,處理器開始使用64位地址,于是又引入了64位模式以區(qū)別于前期的保護(hù)模式。為了在64位模式中運(yùn)行保護(hù)模式的程序,又引入了兼容模式。

IA-32體系結(jié)構(gòu)提供了3種操作模式和1種準(zhǔn)操作模式。實(shí)模式是與8086兼容的操作模式,但有一些擴(kuò)展。保護(hù)模式是處理器的一種最基本的操作模式,在這種模式中,處理器的所有指令以及體系結(jié)構(gòu)的所有特色都是可用的,并且能夠達(dá)到最高的性能。系統(tǒng)管理模式是一種特殊的操作模式,是提供給操作系統(tǒng)的一種透明管理機(jī)制,用于實(shí)現(xiàn)電源管理等特殊操作。虛擬8086模式是一個準(zhǔn)操作模式,允許處理器在保護(hù)模式中執(zhí)行實(shí)模式的程序。

Intel64體系結(jié)構(gòu)又新增了一種IA-32e操作模式,該操作模式又包含兩種子模式,即兼容模式和64位模式。當(dāng)處理器運(yùn)行在兼容模式時,它可以不加修改地運(yùn)行大多數(shù)IA-32體系結(jié)構(gòu)的程序。當(dāng)處理器運(yùn)行在64位模式時,它可以使用64位的線性地址空間和一些新增加的特性。IA-32e不再支持虛擬8086模式。

處理器加電或Reset后的默認(rèn)操作模式是實(shí)模式。操作系統(tǒng)內(nèi)核的初始化部分負(fù)責(zé)將處理器由實(shí)模式切換到其它模式。實(shí)模式和保護(hù)模式之間的轉(zhuǎn)換由控制寄存器CR0中的PE位控制;保護(hù)模式與IA-32e模式之間的轉(zhuǎn)換由IA32_EFER寄存器中的LME和CR0中的PG位控制;兼容模式與64位模式之間的轉(zhuǎn)換由代碼段寄存器CS中的L位控制;保護(hù)模式和虛擬8086模式之間的轉(zhuǎn)換由標(biāo)志寄存器EFLAGS中的VM位控制。

進(jìn)入系統(tǒng)管理模式的唯一途徑是SMI中斷。在系統(tǒng)管理模式中執(zhí)行指令RSM會將處理器切換回原來的操作模式。操作模式之間的轉(zhuǎn)換關(guān)系如圖2.2所示。

圖2.2處理器操作模式之間的轉(zhuǎn)換2.2.2段頁式內(nèi)存管理

IA-32體系結(jié)構(gòu)提供了極為復(fù)雜的段頁式內(nèi)存管理機(jī)制,即先分段,再分頁。其中段式管理是默認(rèn)、必須的,頁式管理是可選的。保留段式是為了兼容,提供頁式是為了支持虛擬內(nèi)存。

在段式管理中,處理器可尋址的線性內(nèi)存空間被劃分成了若干個大小不等的段。一個段就是線性地址空間中的一個連續(xù)的區(qū)間。段中可保存代碼、數(shù)據(jù)、堆?;蚱渌到y(tǒng)級的數(shù)據(jù)結(jié)構(gòu)。段的屬性信息由與之對應(yīng)的段描述符描述。段描述符是一個數(shù)據(jù)結(jié)構(gòu),其一般格式如圖2.3所示。

圖2.3段描述符結(jié)構(gòu)描述符中的第2、3、4、7字節(jié)組成了段的基地址(Baseaddress),用于定義段在線性地址空間中的開始位置?;刂房梢栽?~4GB之間浮動。

描述符中的第0、1字節(jié)和6字節(jié)的低4位(共20位)組成了段界限(Segmentlimit),用于定義段的長度。長度的單位由粒度(G)位表示。當(dāng)G為0時,段以字節(jié)為單位,最大段長為1MB;當(dāng)G為1時,段以頁(4KB)為單位,最大段長為4GB。

DPL是段的特權(quán)級,其值在0~3之間。

S是系統(tǒng)標(biāo)志,用于區(qū)分段的類別,0表示系統(tǒng)段,1表示用戶段。

Type是段的類型。對系統(tǒng)段,類型域由4位組成,可表示16個系統(tǒng)段類型之一,如2表示LDT、9表示32位有效TSS、B表示32位忙TSS、E表示中斷門、F表示陷阱門等。對用戶段,類型域中的4位(0~3,3為高位)被重新解釋如下:

(1)第3位為0表示數(shù)據(jù)段。此時,第2位表示地址擴(kuò)展方向(0表示向大方向擴(kuò)展,1表示向小方向擴(kuò)展),第1位表示段是否可寫(0表示不能寫,1表示可寫)。

(2)第3位為1表示代碼段。此時,第2位是相容標(biāo)志(0表示非相容,1表示相容),第1位是可讀位(表示代碼段是否允許讀,1表示允許,0表示不允許)。保護(hù)模式的代碼段不允許寫。

(3)第0位是存取位,0表示段尚未被存取過,1表示段已被存取過。

D/B標(biāo)志表示有效地址和操作數(shù)的長度。

L標(biāo)志僅出現(xiàn)在IA-32e模式的代碼段描述符中,用于表示執(zhí)行該段代碼時處理器的操作子模式,1表示64位模式,0表示兼容模式。

堆棧段通常是向下擴(kuò)展的、可讀寫的數(shù)據(jù)段。

按照Intel的設(shè)想,每個進(jìn)程(Intel稱任務(wù))都可以定義自己的代碼段、數(shù)據(jù)段等,而每個段都需要描述符,因而系統(tǒng)中會有許多描述符。為了便于管理,Intel用段描述符表來組織系統(tǒng)中的描述符。段描述符表是一個段描述符的數(shù)組,大小可變,最大可達(dá)64KB,最多可保存8192個8字節(jié)的段描述符。段描述符表又分為兩大類,即全局描述符表和局部描述符表。全局描述符表(GDT)中的描述符是全局共用的,其中的第0個描述符保留不用(全為0)。在系統(tǒng)進(jìn)入保護(hù)模式之前必須為其定義一個GDT。由于GDT本身僅是線性地址空間中的一個數(shù)據(jù)結(jié)構(gòu),沒有對應(yīng)的描述符,因而IA-32體系結(jié)構(gòu)專門定義了GDTR寄存器來存放當(dāng)前GDT的信息。

與GDT不同,局部描述符表(LDT)是系統(tǒng)段,其中可存放局部的段描述符,如進(jìn)程自己的代碼段、數(shù)據(jù)段等。定義LDT的描述符叫LDT描述符,出現(xiàn)在GDT中。IA-32體系結(jié)構(gòu)專門提供了一個LDTR寄存器,用于保存當(dāng)前使用的LDT的信息。有了描述符表之后,可以用描述符在GDT或LDT中的索引來標(biāo)識它,這種標(biāo)識稱為段選擇符。段選擇符是16位的標(biāo)識符,它的第3~15位是索引,表示描述符在GDT或LDT中的位置;第2位是指示器(TI),表示索引所對應(yīng)的描述符表(0表示GDT,1表示LDT);第0、1兩位是請求特權(quán)級RPL。

一個段選擇符加上一個偏移量可以唯一地標(biāo)識一個邏輯地址。邏輯地址是程序中使用的地址,不是線性地址,也不是物理地址,在使用之前必須對其進(jìn)行轉(zhuǎn)換。轉(zhuǎn)換的過程是:以段選擇符為索引查描述符表,獲得段的描述符,從中取出段的基地址,將其加上偏移量即可得到與之對應(yīng)的線性地址,如圖2.4所示。圖2.4邏輯地址到線性地址的轉(zhuǎn)換如果沒有啟動分頁機(jī)制,經(jīng)過段描述符轉(zhuǎn)換后的線性地址就是物理地址。

邏輯地址的轉(zhuǎn)換極為頻繁,應(yīng)該將最近使用的描述符緩存起來,以加快地址轉(zhuǎn)換的速度。為此,IA-32體系結(jié)構(gòu)提供了6個段寄存器,即CS、SS、DS、ES、FS和GS,每個段寄存器中可以緩存一個段描述符。有了段寄存器后,在使用一個段之前,可以先將它的描述符裝入到一個段寄存器中。如此以來,邏輯地址就變成了段寄存器+偏移量,邏輯地址到線性地址的轉(zhuǎn)換可以在處理器中完成,不需要再查描述符表。雖然以段為單位可以進(jìn)行內(nèi)存管理,但這種方法比較笨拙,而且與現(xiàn)代虛擬內(nèi)存管理的思想不符,因而IA-32體系結(jié)構(gòu)允許對段內(nèi)的內(nèi)存進(jìn)行再分頁,即在段式管理的基礎(chǔ)上再增加一套頁式管理,也就是段頁式管理。

在段頁式管理中,段內(nèi)的線性地址空間被分割成大小相等的線性頁(4KB、

4MB或2MB等),物理內(nèi)存空間也被分成同樣大小的物理頁。操作系統(tǒng)內(nèi)核維護(hù)一個頁表,用于管理線性頁到物理頁的映射。在頁表中,一個線性頁可以有對應(yīng)的物理頁,此時利用頁表可以完成線性地址到物理地址的轉(zhuǎn)換。一個線性頁也可以沒有對應(yīng)的物理頁,此時的線性地址無法直接轉(zhuǎn)換成物理地址,處理器會產(chǎn)生一個fault異常。操作系統(tǒng)內(nèi)核在處理這種異常時,可以臨時為線性頁分配物理頁并在其中填入適當(dāng)?shù)膬?nèi)容。異常處理之后,線性地址即可以轉(zhuǎn)換成物理地址。

頁表可能很大,因而又被分成多級,如IA-32體系結(jié)構(gòu)將它的頁表分成兩級,即頁目錄和頁表。頁目錄是一個數(shù)組,其元素叫頁目錄項(xiàng)(PDE),每個頁目錄項(xiàng)描述一個頁表。頁目錄的大小為一頁(4KB),頁目錄項(xiàng)的大小為4字節(jié),所以一個頁目錄中有1024個頁目錄項(xiàng),最多可描述1024個頁表。頁表也是一個數(shù)組,其元素叫頁表項(xiàng)(PTE),每個頁表項(xiàng)描述一個線性頁。頁表大小為一頁(4KB),頁表項(xiàng)的大小為4字節(jié),所以一個頁表最多可描述1024個線性頁。

由于物理頁是預(yù)先劃分好的,其開始位置一定在4KB的邊界上,因而頁目錄和頁表項(xiàng)的低12位肯定是0,可以用它們存儲頁表或頁的管理控制信息,如是否有對應(yīng)的物理頁、是否可寫等。頁目錄項(xiàng)與頁表項(xiàng)的結(jié)構(gòu)基本相同。圖2.5是頁表項(xiàng)的結(jié)構(gòu)。圖2.5頁表項(xiàng)的結(jié)構(gòu)在頁表項(xiàng)結(jié)構(gòu)中,P是存在位,表示它所描述的頁表或頁目前是否在物理內(nèi)存中,1表示在內(nèi)存,0表示不在內(nèi)存;R/W是讀寫標(biāo)志位,表示頁表或頁是否允許寫,0表示只讀,1表示可讀可寫;U/S是用戶標(biāo)志位,表示頁表或頁的特權(quán)級,0表示超級用戶(特權(quán)級為0、1、2),1表示普通用戶(特權(quán)級為3);A是存取標(biāo)志位,表示頁表或頁有沒有被存取(讀、寫)過,當(dāng)頁表或頁被存取時,處理器自動設(shè)置該標(biāo)志;D是臟標(biāo)志位,表示頁是否被修改過,當(dāng)頁被修改時,處理器自動設(shè)置該標(biāo)志。

在頁目錄項(xiàng)中,PAT標(biāo)志變成了PS標(biāo)志,表示物理頁的尺寸。線性地址到物理地址轉(zhuǎn)換的過程是:按照頁表層次將線性地址劃分成多個片段;從最高片段開始,以片段值為索引逐個查對應(yīng)的頁表,獲得下一級頁表的位置;查最后一級頁表,獲得物理頁的位置;將物理頁的開始地址加上最后一個片段的值(偏移量)得到的就是線性地址對應(yīng)的物理地址。圖2.6是利用二級頁表進(jìn)行地址轉(zhuǎn)換的過程。圖2.6線性地址到物理地址的轉(zhuǎn)換(4KB頁)在做線性地址到物理地址的轉(zhuǎn)換時,必須知道所用頁目錄的位置。IA-32體系結(jié)構(gòu)專門提供了一個CR3寄存器,用于存放當(dāng)前使用的頁目錄的物理地址,因此CR3又叫頁目錄基地址寄存器(PDBR)。在啟動分頁機(jī)制之前,必須定義好頁目錄并將其基地址裝入到CR3寄存器中。只要進(jìn)程在活動,它的頁目錄就應(yīng)該一直駐留在內(nèi)存。

當(dāng)然,頁目錄項(xiàng)也可以直接指向物理頁,此時的頁大小是4MB。采用4MB頁可加快地址轉(zhuǎn)換的速度,因而通常將操作系統(tǒng)內(nèi)核所占用的頁設(shè)為4MB頁。當(dāng)頁目錄項(xiàng)的PS位為1時,它所描述的是一個4MB頁而不再是一個頁表。

頁式管理機(jī)制是由操作系統(tǒng)內(nèi)核啟動的,啟動的方法是將CR0中的PG標(biāo)志置1。啟動分頁機(jī)制之后,每個線性地址都需要經(jīng)過頁目錄和頁表的轉(zhuǎn)換,這顯然會大大降低內(nèi)存訪問的速度。為解決這一問題,IA-32體系結(jié)構(gòu)在其處理器芯片中增加了一個高速緩存TLB(TranslationLookasideBuffers),在其中存儲最近使用的頁目錄和頁表項(xiàng)。地址轉(zhuǎn)換時,首先查TLB,如其中有緩存的頁表項(xiàng),可立刻進(jìn)行地址轉(zhuǎn)換;只有當(dāng)TLB中沒有對應(yīng)的頁表項(xiàng)時,才會訪問頁目錄和頁表。新訪問的頁表項(xiàng)會被自動加入TLB。

TLB的內(nèi)容必須經(jīng)常刷新以保持與頁目錄和頁表的一致。刷新工作由操作系統(tǒng)內(nèi)核負(fù)責(zé)。當(dāng)頁目錄或頁表項(xiàng)改變時,內(nèi)核必須立刻使TLB中的相應(yīng)項(xiàng)失效。特別地,當(dāng)CR3改變時,TLB中的所有內(nèi)容(Global頁除外)會自動失效。INVLPG指令可以將TLB中的指定項(xiàng)設(shè)為無效。2.2.3內(nèi)存管理的變化與擴(kuò)展

段頁式內(nèi)存管理是Intel處理器提供的最基礎(chǔ)的內(nèi)存管理機(jī)制,在此基礎(chǔ)上,Intel還提供了許多變化與擴(kuò)展。

(1)基本平板式內(nèi)存管理?;舅悸肥瞧帘蔚舳问焦芾恚耆捎庙撌焦芾?。做法是定義一個代碼段、一個數(shù)據(jù)段,兩個段的基地址都是0,大小都是4GB。如此以來,邏輯地址就是線性地址,段式管理的作用被屏蔽。對段內(nèi)內(nèi)存(實(shí)際是所有內(nèi)存)的管理完全依靠分頁機(jī)制。

(2)保護(hù)平板式內(nèi)存管理?;舅悸肥瞧帘蔚舳问焦芾?,但保留一些保護(hù)特征,主要采用頁式管理。一種典型的做法是定義四個段,即內(nèi)核代碼段、內(nèi)核數(shù)據(jù)段、用戶代碼段、用戶數(shù)據(jù)段,四個段的基地址都是0,大小都是4GB,不同的是內(nèi)核段的特權(quán)級是0,用戶段的特權(quán)級是3。將操作系統(tǒng)內(nèi)核的代碼和數(shù)據(jù)都放在內(nèi)核段中,將所有用戶的代碼和數(shù)據(jù)都放在用戶段中(不做區(qū)分)。進(jìn)程執(zhí)行用戶代碼時使用用戶段,執(zhí)行內(nèi)核代碼時使用內(nèi)核段。段的地址轉(zhuǎn)化作用被屏蔽,但基于特權(quán)級的保護(hù)特征被保留,如用戶代碼不能訪問內(nèi)核數(shù)據(jù)等。段內(nèi)內(nèi)存用頁式管理。一般情況下,每個進(jìn)程都會用到四個段,因而需要為每個進(jìn)程定義四套頁目錄/頁表。但對同一個進(jìn)程來說,由于它對四個段的使用絕對不會重疊,因而可以將四個段疊加起來,看成是進(jìn)程的平板地址空間,同時也可將四套頁目錄/頁表合并為一套。在合并后的頁目錄/頁表中,頁表項(xiàng)可能代表不同段中的頁。頁表項(xiàng)中的U/S標(biāo)志用于區(qū)分用戶頁和內(nèi)核頁。如果操作系統(tǒng)內(nèi)核能保證各進(jìn)程的頁目錄/頁表中沒有重疊的表項(xiàng),就可以保證進(jìn)程之間的相互隔離。

(3)多段式內(nèi)存管理。基本思路是完全采用段式管理,屏蔽掉頁式管理。

(4)基于物理地址擴(kuò)展的頁式內(nèi)存管理。從PentiumPro開始,IA-32體系結(jié)構(gòu)引入了物理地址擴(kuò)展(PAE)機(jī)制以支持36位物理地址。在該管理模式中,處理器可訪問的物理地址空間被擴(kuò)充到了64GB,但線性地址空間仍然為4GB,然而一個線性頁可以映射到任意一個物理頁。為做到這一點(diǎn),頁目錄和頁表項(xiàng)被擴(kuò)充到了64位,因而一個頁目錄或頁表中的項(xiàng)數(shù)變成了512,一個頁目錄僅能描述1GB的線性地址空間,4GB的線性地址空間需要4個頁目錄描述。新引入一個僅有4個表項(xiàng)的頁目錄指針表PDP(Directory-PointerTable),其中的每個表項(xiàng)指向一個頁目錄(一個PDP可以描述4GB的空間),CR3指向PDP。地址轉(zhuǎn)換機(jī)制被修改,以便將32位線性地址翻譯成36位物理地址。當(dāng)頁目錄項(xiàng)中的PS位被置1時,它描述的頁變成了2MB頁。

(5)

64位平板內(nèi)存管理。在64位模式中,段通常被關(guān)閉,雖然不是完全關(guān)閉。處理器將CS、DS、ES、SS的基地址統(tǒng)統(tǒng)看成0,而且不再做段界限檢查,因而邏輯地址就是線性地址。但FS和GS的基地址可以不是0。如果FS、GS的基地址不是0,在將邏輯地址轉(zhuǎn)換成線性地址時要加上FS、GS的基地址。值得注意的是,F(xiàn)S、GS的基地址是64位地址(兼容模式只用它的低32位),記錄在MSR中。在64位模式中,對內(nèi)存的管理完全依靠分頁機(jī)制。Intel64體系結(jié)構(gòu)擴(kuò)展了PAE機(jī)制,使之能支持64位線性地址和52位物理地址。主要的擴(kuò)展包括:頁目錄指針表(PDP)被擴(kuò)充到了512項(xiàng);新引入一個第四級頁映射表PML4(PageMapLevel4Table),它的每個表項(xiàng)可指向一個PDP;所有四級頁表的表項(xiàng)都被擴(kuò)充到了64位(PAE必須使能);頁目錄項(xiàng)中的PS標(biāo)志用于控制4KB和2MB頁;CR3指向PML4;在所有頁表項(xiàng)的第63位上新增了一個執(zhí)行禁止標(biāo)志EXB(Execute-DisableBit),如果該標(biāo)志被置1,它對應(yīng)的頁只能用做數(shù)據(jù)頁,不能用做代碼頁,即其上的代碼被禁止執(zhí)行。2.2.4內(nèi)存保護(hù)

一旦保護(hù)機(jī)制被啟動,處理器就會對每一次內(nèi)存訪問進(jìn)行保護(hù)性檢查,以確保所有的訪問都滿足保護(hù)策略。當(dāng)發(fā)現(xiàn)違反內(nèi)存保護(hù)約定的內(nèi)存訪問時,處理器會產(chǎn)生異常。由于保護(hù)檢查和地址轉(zhuǎn)換是并行進(jìn)行的,因而檢查本身并不會帶來額外的開銷。

保護(hù)檢查包括段級檢查和頁級檢查兩種,檢查的依據(jù)是段描述符、頁目錄和頁表,檢查的順序是先段后頁,檢查的基礎(chǔ)是特權(quán)級。特權(quán)級是Intel為實(shí)現(xiàn)保護(hù)而定義的特權(quán)編號,從0到3,其中0是最高特權(quán)級,3是最低特權(quán)級。系統(tǒng)中每個段(代碼段、數(shù)據(jù)段、堆棧段等)都有特權(quán)級,因而系統(tǒng)中所有的程序與所有的數(shù)據(jù)也都有特權(quán)級。

段一級的檢查包括段界限檢查、段類型檢查、特權(quán)級檢查、長指針檢查等。段一級檢查的原則是:

(1)低特權(quán)級的代碼不能訪問高特權(quán)級的數(shù)據(jù)。

(2)高特權(quán)級的代碼可以訪問低特權(quán)級的數(shù)據(jù)。

(3)代碼只能使用與其特權(quán)級相同的堆棧,當(dāng)特權(quán)級切換時,堆棧也要隨之切換。

(4)只能向具有相同特權(quán)級的非相容代碼段轉(zhuǎn)移控制(長JMP和長CALL)。

(5)可以向具有同等或較高特權(quán)級的相容代碼段轉(zhuǎn)移控制,但不能向具有較低特權(quán)級的相容代碼段轉(zhuǎn)移控制(長JMP和長CALL)。

(6)即使使用調(diào)用門、中斷門或陷阱門,也不能從高特權(quán)級向低特權(quán)級轉(zhuǎn)移控制。

(7)不允許用長RET向高特權(quán)級轉(zhuǎn)移控制。頁一級的檢查包括特權(quán)級檢查和讀寫檢查,相關(guān)的標(biāo)志是頁目錄/頁表項(xiàng)中的U/S和R/W位。U/S為0的頁是超級頁,U/S為1的頁是用戶頁。一般情況下,超級頁中的代碼可以訪問所有的頁(不管R/W標(biāo)志),用戶頁中的代碼只能訪問用戶頁。當(dāng)CR0.WP被置1時,超級頁中的代碼也不能寫只讀的用戶頁。

在Intel64體系結(jié)構(gòu)中,新引入了執(zhí)行禁止標(biāo)志NXB,用于防止緩沖區(qū)溢出之類的攻擊。將IA32_EFER.NXE置1可使能頁級執(zhí)行檢查,此后NXB被置1的頁僅能用作數(shù)據(jù)頁,試圖執(zhí)行數(shù)據(jù)頁中的指令會引起處理器異常。2.2.5進(jìn)程管理

當(dāng)處理器運(yùn)行在保護(hù)模式時,它的所有工作都是在任務(wù)進(jìn)程中完成的,因而至少需定義一個任務(wù)。任務(wù)由執(zhí)行空間和任務(wù)狀態(tài)段組成。執(zhí)行空間由代碼段、堆棧段和數(shù)據(jù)段表示,它們的描述符可直接放在GDT或任務(wù)的LDT中。任務(wù)狀態(tài)段(TSS)用于記錄任務(wù)的狀態(tài),如通用寄存器及EFAGS、EIP、CR3、LDTR、TR寄存器的狀態(tài),0、1、2級堆棧的棧底指針等。一個TSS可唯一地描述一個任務(wù),如圖2.7所示。

圖2.732位任務(wù)狀態(tài)段(TSS)在IA-32e模式中,TSS被大大精簡,僅剩余了三個棧底指針和一個I/O許可位圖,但新增加了一個中斷棧表(7個棧指針)。

任務(wù)狀態(tài)段由專門的TSS描述符描述。當(dāng)前任務(wù)的TSS描述符被記錄在專門的任務(wù)寄存器(TR)中。通過TSS描述符或任務(wù)門,利用CALL、JMP等指令可自動完成任務(wù)切換,但代價很高。現(xiàn)代操作系統(tǒng)內(nèi)核都選用代價更低的切換方法,如通過指令保存和恢復(fù)必要的寄存器從而完成任務(wù)切換。64位模式已不再支持自動任務(wù)切換。由于CR3在TSS中,因而當(dāng)進(jìn)程切換時,頁目錄也會隨著切換,也就是說,每個進(jìn)程都可以有自己獨(dú)立的線性地址空間。但為了系統(tǒng)能夠正常運(yùn)行,在任何時候處理器都應(yīng)該能夠訪問到所有進(jìn)程的TSS,即應(yīng)該將TSS保存在所有進(jìn)程都可訪問到的共享地址空間中。進(jìn)一步地,GDT、IDT、操作系統(tǒng)內(nèi)核代碼和系統(tǒng)管理信息等都應(yīng)該保存在共享地址空間中。事實(shí)上,在所有進(jìn)程的頁目錄中,確實(shí)存在著共用的頁表,這些共用的頁表描述的就是進(jìn)程間共享的線性地址空間。2.2.6中斷處理

Intel的中斷是外部中斷、異常和陷入的統(tǒng)稱。外部中斷來自處理器之外的硬件,如外設(shè),是隨機(jī)的;異常來自于處理器內(nèi)部,表示在處理器執(zhí)行指令的過程中檢測到了某種錯誤條件(如被0除、段越界等);陷入來自程序,由INTn、INTO等指令產(chǎn)生。外部中斷可以被屏蔽,但陷入和異常不能被屏蔽。屏蔽中斷的方法是清除EFLAGS寄存器中的IF標(biāo)志。中斷的產(chǎn)生表示系統(tǒng)中出現(xiàn)了某種必須引起處理器關(guān)注的事件,處理器需要立刻離開當(dāng)前的工作轉(zhuǎn)去處理這些事件。處理中斷的程序稱為中斷處理器程序,處理異常的程序稱為異常處理程序,處理陷入的程序稱為系統(tǒng)調(diào)用服務(wù)程序。處理程序可位于內(nèi)核空間的任意位置,且可有不同的特權(quán)級,因而需要專門的數(shù)據(jù)結(jié)構(gòu)來描述它們。Intel處理器稱處理程序的入口為門(Gate)。可用的門有三類,分別是中斷門、陷阱門和任務(wù)門(Linux只用到了中斷門和陷阱門)。中斷門和陷阱門是進(jìn)入中斷和異常處理程序的門戶,分別由中斷門和陷阱門描述符定義。中斷門描述符的格式如圖2.8所示。圖2.8中斷門描述符陷阱門描述符與中斷門描述符的格式基本一致,所不同的是陷阱門描述符中的類型是“D111”。類型中的D表示位數(shù),0為16位,1為32位。在兩種門描述符中,選擇符與偏移量合起來定義了一個處理器程序的入口地址,DPL定義了門的特權(quán)級。

中斷門與陷阱門的功能也基本一致,都定義了一個處理程序的入口地址,所不同的是處理IF標(biāo)志的方式。當(dāng)通過中斷門進(jìn)入處理程序時,IF標(biāo)志被清掉(中斷被關(guān)閉);當(dāng)通過陷阱門進(jìn)入處理程序時,IF標(biāo)志保持不變。為了處理中斷,Intel處理器給它的每個中斷和異常都賦予了一個中斷向量號,并定義一個中斷描述符表(IDT)用于建立中斷向量號和門之間的對應(yīng)關(guān)系。

Intel處理器定義的中斷向量號共256個,其中0~31被處理器保留,主要用于異常和不可屏蔽中斷(NMI),32~255可由操作系統(tǒng)內(nèi)核自由使用,如賦給外設(shè)等。

IDT是一個描述符數(shù)組,由一組門描述符組成,每一個中斷向量號對應(yīng)其中的一個門描述符。因?yàn)橹挥?56個中斷和異常,所以IDT只有256項(xiàng)。與GDT相同,IDT也不是一個段,沒有對應(yīng)的段描述符。IDT可以駐留在線性地址空間的任何位置。Intel處理器專門提供了一個IDTR寄存器來記錄IDT的基地址和界限信息。當(dāng)中斷或異常發(fā)生時,處理器以中斷向量號為索引查IDT可得到與之對應(yīng)的門描述符,從而可得到處理程序的入口地址。圖2.9是IDTR和IDT之間關(guān)系。圖2.9IDTR與IDT之間的關(guān)系外部中斷被處理完后,處理器會接著執(zhí)行被中斷指令的下一條指令。陷入指令被處理完后,處理器會接著執(zhí)行陷入指令的下一條指令。異常處理程序被執(zhí)行完后,處理器的返回位置依賴于異常的類型。Intel處理器目前定義了三類共20個異常。故障(Fault)類異常是可以更正的,當(dāng)故障類異常被處理完后,處理器會重新執(zhí)行產(chǎn)生故障的指令。陷阱(Trap)類異常是由特殊的陷入指令(INT3、INTO)引發(fā)的,當(dāng)陷阱類異常被處理完后,處理器會接著執(zhí)行陷入指令的下一條指令。中止(Abort)類異常是嚴(yán)重的錯誤,處理器無法保證程序能夠繼續(xù)正常執(zhí)行。

Intel規(guī)定,通過中斷門或陷阱門只能向同級或更高特權(quán)級的代碼段轉(zhuǎn)移控制,因而通常將處理程序定義在0特權(quán)級的代碼段(內(nèi)核代碼段)中。

通過陷入指令也可以進(jìn)入中斷或異常處理程序,但要進(jìn)行特權(quán)級檢查,要求進(jìn)入前的特權(quán)級(CPL)必須小于或等于門描述符的特權(quán)級(DPL)。中斷或陷阱門的DPL通常被設(shè)為0,因而用戶程序無法通過INTn指令直接進(jìn)入中斷或異常處理程序。中斷或異常處理程序運(yùn)行在當(dāng)前進(jìn)程的上下文中,但可能使用不同的堆棧。如果中斷或異常發(fā)生時處理器在第0特權(quán)級上(在執(zhí)行內(nèi)核),則處理程序可直接使用當(dāng)前進(jìn)程的系統(tǒng)堆棧,不用切換堆棧。如果中斷或異常發(fā)生時處理器在第3特權(quán)級上(在執(zhí)行用戶程序),則需要切換堆棧,即從當(dāng)前進(jìn)程的用戶堆棧切換到它的系統(tǒng)堆棧。TR中記錄著當(dāng)前進(jìn)程的TSS,其中包含當(dāng)前進(jìn)程的系統(tǒng)堆棧的棧底。

中斷或異常發(fā)生時,處理器會自動在棧頂壓入一些參數(shù),其中EFLAGS是中斷或異常發(fā)生前的系統(tǒng)狀態(tài),SS:ESP是中斷或異常發(fā)生前的用戶堆棧棧頂,CS:EIP是中斷或異常的返回地址。只有發(fā)生堆棧切換時,才會在棧頂壓入SS:ESP。

僅有一些特殊的異常會在棧頂壓入error-code(見表3.3),外部中斷和陷入不會自動在棧頂壓入error-code。為了使堆棧保持平衡,對不自動壓入error-code的中斷和異常,處理程序應(yīng)在棧頂壓入一個值,如Linux的外部中斷處理程序會壓入中斷向量號、無error-code的異常處理程序會壓入0或-1。圖2.10是中斷發(fā)生時堆棧的變化情況。圖2.10中斷或異常發(fā)生時的堆棧變化在64位模式中,中斷和異常的處理方式有所改變,如處理程序必須在64位代碼段中,因而中斷和陷阱門描述符被擴(kuò)充到了16字節(jié),其中的偏移量被擴(kuò)充到了64位;IDT中僅有新格式的門描述符;堆棧寬度變成了64位,而且當(dāng)中斷發(fā)生時,會無條件地壓入棧指針(SS:RSP);當(dāng)需要切換堆棧時,新的SS被強(qiáng)制設(shè)為NULL;新增加了的中斷堆棧表(IST)機(jī)制,允許為特定的中斷或異常指定專門的堆棧。2.2.7APIC

高級可編程中斷控制器(AdvancedProgrammableInterruptController,APIC)是老式8259中斷控制器(PIC)的升級產(chǎn)品,APIC本身的升級版分別叫做xAPIC和x2APIC。

APIC采用分布式體系結(jié)構(gòu),由LocalAPIC和I/OAPIC通過專用總線或系統(tǒng)總線互連構(gòu)成,如圖2.11所示。圖2.11APIC結(jié)構(gòu)

I/OAPIC接收來自設(shè)備的中斷,把它們傳遞給選定的一個或一組LocalAPIC。LocalAPIC可以接收外部的(來自I/OAPIC或8259A)、內(nèi)部的(來自LocalAPIC的內(nèi)部時鐘等)或來自其它處理器的(IPI)中斷,并把它們傳遞給處理器核。LocalAPIC通常被集成在處理器核中。每個LocalAPIC有一個唯一的標(biāo)識符(ID),用于標(biāo)識LocalAPIC,也用于標(biāo)識與之關(guān)聯(lián)的處理器核。

LocalAPIC的內(nèi)部中斷通常有5~6個,包括LocalAPIC定時器、溫度傳感器、性能計數(shù)器、內(nèi)部錯誤、LINT0和LINT1等,其中LINT0和LINT1是兩個管腳,用于連接其它中斷源,如8259PIC。LocalAPIC提供了一個局部中斷向量表,用于設(shè)定各個內(nèi)部中斷的向量號、遞交方式等。可以將LINT0或LINT1的中斷遞交方式設(shè)為ExtINT,此時處理器認(rèn)為該管腳上連接的是PIC,會按通常的應(yīng)答方式獲取中斷向量號。

對操作系統(tǒng)內(nèi)核來說,LocalAPIC由一組寄存器構(gòu)成,包括LocalAPICID、LocalAPICVersion、TaskPriorityRegister(TPR)、ProcessorPriorityRegister(PPR)、In-ServiceRegister(ISR)、InterruptRequestRegister(IRR)、InterruptCommandRegister(ICR)、LocalVectorTable(LVT)、EndofInterruptRegister等。這些寄存器被映射到處理器物理地址空間中的一個4KB大小的區(qū)域內(nèi),缺省的起始地址為0xFEE00000。操作系統(tǒng)內(nèi)核可以將APIC的寄存器重新映射到物理地址空間的其它區(qū)域。LocalAPIC的狀態(tài)和寄存器基地址記錄在MSR寄存器IA32_APIC_BASE中。

多核處理器系統(tǒng)中有多個LocalAPIC,它們的寄存器都被映射到相同的位置。每一個邏輯處理器都可以訪問該映射頁,但訪問的結(jié)果各不相同。當(dāng)邏輯處理器讀寫LocalAPIC映射頁時,它實(shí)際上訪問的是自己的LocalAPIC。

I/OAPIC也由一組寄存器組成,包括I/OAPICID、I/OAPICVER、I/OAPICARB、I/OREDTBL等。其中I/OREDTBL是一個中斷重定向表,用于確定各外部中斷的遞交目的地、向量號、遞交模式等。與LocalAPIC的寄存器不同,I/OAPIC中的寄存器只能用間接方法訪問,方法是先將要訪問寄存器的偏移量寫入選擇寄存器IOREGSEL,而后再讀或?qū)憯?shù)據(jù)寄存器IOWIN。

每一個I/OAPIC都有一個物理基地址ioapicaddr,這一地址實(shí)際就是該I/OAPIC中寄存器IOREGSEL的物理地址,寄存器IOWIN的物理地址是ioapicaddr+0x10。缺省情況下,ioapicaddr是0xFEC00000。查ACPI或MP表可獲得各I/OAPIC的基地址。2.2.8處理器初始化

從P6系列開始,在IA-32體系結(jié)構(gòu)中增加了一個多處理器初始化協(xié)議(MP),用于規(guī)定多處理器系統(tǒng)的初始化過程。MP協(xié)議將處理器分為自舉處理器BSP(BootstrapProcessor)和應(yīng)用處理器AP(ApplicationProcessor)。在系統(tǒng)加電或Reset之后,多處理器系統(tǒng)中的系統(tǒng)硬件會動態(tài)地選舉出一個處理器為BSP,其余處理器為AP。

MP協(xié)議僅在加電或Reset之后執(zhí)行一次,此后的INIT不會再執(zhí)行MP協(xié)議。MP協(xié)議規(guī)定的處理器初始化過程如下:

(1)根據(jù)系統(tǒng)拓?fù)浣Y(jié)構(gòu),給系統(tǒng)總線上的每一個邏輯處理器一個唯一的8位APICID。該ID號被寫入處理器的局部APICID寄存器中。

(2)根據(jù)APICID為每個邏輯處理器賦予一個唯一的仲裁優(yōu)先級。

(3)各邏輯處理器同時執(zhí)行自己的內(nèi)建自檢代碼BIST。

(4)BIST執(zhí)行完畢之后,系統(tǒng)總線上的各邏輯處理器利用硬件定義的選擇機(jī)制選舉出BSP和AP。而后,BSP開始執(zhí)行BIOS代碼,各AP進(jìn)入等待狀態(tài)。

(5)

BSP創(chuàng)建一個ACPI表和一個MP表,并將它自己的APICID填入其中。

(6)在自舉程序執(zhí)行完后,BSP將處理器計數(shù)器設(shè)置為1,而后向所有的AP廣播SISP消息。SISP消息中包含一個向量,指出各AP開始執(zhí)行的初始化代碼的位置。

(7)AP申請初始化信號量,在獲得信號量后開始執(zhí)行初始化代碼,將自己的APICID填入ACPI表和MP表,將處理器計數(shù)器加1。在初始化代碼執(zhí)行完畢之后,AP執(zhí)行CLI和HLT指令進(jìn)入停止?fàn)顟B(tài)。

(8)在所有AP都執(zhí)行完初始化代碼之后,BSP通過處理器計數(shù)器獲得連接在系統(tǒng)總線上的邏輯處理器數(shù),而后執(zhí)行進(jìn)一步的自舉和啟動代碼,如內(nèi)核初始化代碼等。

(9)在BSP執(zhí)行內(nèi)核初始化代碼期間,各AP一直處于停止?fàn)顟B(tài),等待被BSP的處理器間中斷信號IPI喚醒。2.2.9寄存器與特權(quán)指令I(lǐng)A-32體系結(jié)構(gòu)提供了8個32位的通用寄存器,分別稱為EAX、EBX、ECX、EDX、ESI、EDI、ESP、EBP。Intel64體系結(jié)構(gòu)將這8個通用寄存器擴(kuò)充到了64位,分別稱為RAX、RBX、RCX、RDX、RSI、RDI、RSP、RBP,并另外引入了8個通用寄存器,分別稱為R8、R9、R10、R11、R12、R13、R14、R15。

IA-32體系結(jié)構(gòu)提供了6個段寄存器,即CS、DS、SS、ES、FS、GS。在64模式中,DS、ES、SS已不再使用,F(xiàn)S、GS用于段重載(影響段的基地址),CS用于控制64位模式與兼容模式的切換。在IA-32體系結(jié)構(gòu)中,指令寄存器是EIP,長度為32位,記錄下一條要執(zhí)行的指令地址。在Intel64體系結(jié)構(gòu)中,指令寄存器被擴(kuò)充到了64位,稱為RIP。

IA-32體系結(jié)構(gòu)提供了一個32位的標(biāo)志寄存器EFLAGS,用于存放處理器的狀態(tài)信息(如ZF、CF、OF等)和一些系統(tǒng)控制信息(如IF、IOPL、VM等)。在64位模式中,標(biāo)志寄存器被擴(kuò)充到了64位,稱為RFLAGS,但內(nèi)容并未擴(kuò)展。

IA-32體系結(jié)構(gòu)提供了4個內(nèi)存管理寄存器,分別稱為GDTR、LDTR、IDTR、TR。Intel64體系結(jié)構(gòu)將它們的基地址部分?jǐn)U充到了64位。

IA-32體系結(jié)構(gòu)提供了5個32位的控制寄存器,分別稱為CR0、CR1、CR2、CR3、CR4。其中CR0中包含系統(tǒng)控制標(biāo)志,用于控制處理器的操作模式和狀態(tài),如是否啟用分頁機(jī)制等;CR1保留未用;CR2用于暫存引起頁故障異常(fault)的線性地址;CR3中暫存當(dāng)前使用的頁目錄的物理基地址;CR4中包含一組體系結(jié)構(gòu)擴(kuò)展標(biāo)志,如PAE、PSE等。Intel64體系結(jié)構(gòu)將控制寄存器擴(kuò)充到了64位,新增加了兩個標(biāo)志位,并引入了一個新的控制寄存器CR8,用于記錄任務(wù)的優(yōu)先級。

Intel處理器中還提供了一組專門給操作系統(tǒng)內(nèi)核使用的、與處理器型號相關(guān)的專用寄存器(MSR),用來控制處理器的debug擴(kuò)展、性能監(jiān)視、機(jī)器檢查結(jié)構(gòu)、內(nèi)存類型范圍等。在不同的處理器中,MSR寄存器的個數(shù)和功能可能會有所變化。

Intel處理器還提供了8個調(diào)試寄存器(DR0-DR7),用于幫助Debug程序設(shè)置斷點(diǎn)。除了上述寄存器之外,Intel處理器還專門提供了一組系統(tǒng)指令,用來處理系統(tǒng)級的工作。如裝入系統(tǒng)寄存器、管理Cache、管理中斷、設(shè)置Debug寄存器等。有些系統(tǒng)指令只能由操作系統(tǒng)內(nèi)核執(zhí)行(要求特權(quán)級為0),另一些系統(tǒng)指令可在任意特權(quán)級下執(zhí)行。表2.1中是常用的幾條系統(tǒng)指令。

表2.1常用的系統(tǒng)指令

GNU的C語言是對標(biāo)準(zhǔn)C語言的擴(kuò)展,其編譯器稱為GCC。GCC是RichardStallman于1984年發(fā)起的一個項(xiàng)目,最初的目的是開發(fā)一個免費(fèi)的

C

編譯器,因而早期的意思是GNUCCompiler。由于GCC具有靈活的架構(gòu)并采取了開源策略,因而發(fā)布之后迅速被接受、移植、擴(kuò)充,目前已可支持C、C++、Objective-C、Objective-C++、Java、Fortran、Ada等多種語言,支持30多種處理器家族,可在超過60種平臺上運(yùn)行?,F(xiàn)在GCC的意思變成了GNUCompilerCollection。2.3GNUC語言

GCC中的C編譯器是Linux的基礎(chǔ),Linux內(nèi)核源代碼只能用GCC編譯。GCC支持C語言標(biāo)準(zhǔn)C89、C90、C94、C95、C99等,并有自己的擴(kuò)展,如:

(1)允許將一個復(fù)合語句括在一對圓括號內(nèi)作為一個表達(dá)式使用,如:

#definemaxint(a,b)({int_a=(a),_b=(b);_a>_b?_a:_b;})

(2)允許在一個函數(shù)內(nèi)部定義嵌入式函數(shù),如:

foo(doublea,doubleb){

double square(doublez){returnz*z;}

return square(a)+square(b);

}

(3)可以直接將typeof作數(shù)據(jù)類型使用,如:

typeof(*x)y[4]; //指針數(shù)組,指針?biāo)割愋褪菂?shù)x的類型

typedef typeof(expr)T; //T是expr的類型

(4)可以忽略條件表達(dá)式的中間項(xiàng)。如x?:y等價于x?x:y。

(5)允許用longlongint聲明64位長整數(shù),用unsignedlonglongint聲明64位無符號長整數(shù)。后綴LL表示64位整常量,ULL表示64位無符號整常量。

(6)允許聲明長度為0的數(shù)組。0長度數(shù)組通常作為結(jié)構(gòu)的最后一個成員,用于表示一組變長的對象。0長度數(shù)組不占用結(jié)構(gòu)的空間,如:

structline{

int length;

char contents[0];

};

(7)允許在宏定義中使用可變數(shù)目的參數(shù),如:

#definedebug(format,args...)fprintf(stderr,format,args)

(8)允許在數(shù)組、結(jié)構(gòu)、聯(lián)合等類型變量的聲明中使用指示初始化,如:

structpointp={.y=yvalue,.x=xvalue};

僅給x和y成員初值,未明確聲明成員的初值是0。

(9)在定義函數(shù)時,允許用關(guān)鍵字_attribute_聲明屬性。可以聲明的屬性包括對齊要求(aligned)、即將過時(deprecated)、快速調(diào)用(fastcall)、無返回(noreturn)、兩次返回(returns_twice)、函數(shù)所在節(jié)(section)、用寄存器傳遞參數(shù)的個數(shù)(regparm)等。如:

voidfatal() _attribute_((noreturn));

#define_init _attribute_((_section_(".init.text")))

#defineasmlinkage _attribute_((regparm(0)))第一個聲明表示函數(shù)fatal不返回,宏_ini表示將函數(shù)代碼放在.init.text節(jié)中,宏asmlinkage表示函數(shù)不用寄存器傳遞參數(shù),所有的參數(shù)都在堆棧中傳遞。

C語言函數(shù)之間通常用寄存器傳遞參數(shù),regparm(n)表示用寄存器傳遞n個參數(shù),前三個分別在EAX、EBX、ECX寄存器中。

(10)在定義結(jié)構(gòu)或聯(lián)合類型時,可以通過關(guān)鍵字_attribute_聲明相關(guān)的屬性。在聲明變量或結(jié)構(gòu)成員時,可以通過關(guān)鍵字_attribute_聲明相關(guān)的屬性,如對齊要求、存放變量的節(jié)等。

(11)在內(nèi)嵌式匯編程序中,允許用C語言表達(dá)式做指令的操作數(shù),不用關(guān)心數(shù)據(jù)的存儲位置(見2.4.3節(jié))。

(12)提供了數(shù)百個內(nèi)建函數(shù)(多以_builtin_開頭),用于實(shí)現(xiàn)內(nèi)存原子存取、對象大小檢查、程序優(yōu)化等,如:

#definelikely(x) _builtin_expect(!!(x),1) //x的預(yù)期值是真

#defineunlikely(x) _builtin_expect(!!(x),0) //x的預(yù)期值是假

語句if(unlikely(exp){…} 表示exp很少為真,編譯器可據(jù)此進(jìn)行優(yōu)化。

Linux內(nèi)核中的絕大部分代碼是用GNU的C語言寫成的,但也包含一些匯編程序。Linux中的匯編程序可以以.S文件的形式單獨(dú)出現(xiàn),也可以嵌入在C代碼中。Linux中的匯編程序采用GNU的匯編格式,語法上符合AT&T規(guī)范。2.4GNU匯編語言2.4.1GNU匯編格式

GNU匯編程序由匯編指令、匯編指示、符號、常量、表達(dá)式、注釋等組成。為了便于管理和鏈接,通常將目標(biāo)程序中的代碼、數(shù)據(jù)等組織成不同的節(jié)(section)。節(jié)是具有相同屬性(如只讀)的一段連續(xù)的地址區(qū)間(在節(jié)中使用相對地址)。匯編器根據(jù)源程序中的匯編指示將源程序轉(zhuǎn)變成由若干個節(jié)組成的目標(biāo)文件,鏈接器負(fù)責(zé)將所有目標(biāo)文件中相同節(jié)的內(nèi)容拼接起來形成可執(zhí)行文件。一般情況下,匯編器生成的目標(biāo)文件中至少包含三個節(jié),分別是.text(只讀的程序代碼節(jié))、.data(可讀寫的帶初值的變量節(jié))和.bss(可讀寫的未初始化的變量節(jié))。用戶可以通過匯編指示.section聲明其它的節(jié),以便細(xì)化管理。如在Linux源代碼中經(jīng)常會看到下列格式的程序片段:

1: asminstruction //該指令在.text節(jié)中,它的執(zhí)行可能會出錯

.section_ex_table,"a" //切換到_ex_table節(jié),在其中增加一對數(shù)據(jù)

.align4 //4對齊

.long1b,syscall_fault //異常處理表項(xiàng),意思是當(dāng)1處的指令出錯時,轉(zhuǎn)syscall_fault

.previous //切換回.text節(jié)

另外,在節(jié)中還可以再定義子節(jié),以便更好地組織分散在不同源程序中的、屬于同一個節(jié)的代碼和數(shù)據(jù)。子節(jié)的標(biāo)識是節(jié)名后加一個數(shù)字編號,如.text.2,編號可以從0到8191。在目標(biāo)文件中,一個節(jié)的內(nèi)容就是它的各子節(jié)內(nèi)容的總和(按編號排序),但已沒有子節(jié)的概念。鏈接器只能看到節(jié),已看不到子節(jié)。如未為節(jié)定義子節(jié),匯編器會假定其中只有一個編號為0的子節(jié)??梢杂脜R編指示.subsection切換子節(jié),也可以用子節(jié)的標(biāo)識符切換子節(jié)。在GNU匯編程序中,常量是一個數(shù)字,其值是已知的,可直接使用。常量包括字符、字符串、整數(shù)(二進(jìn)制、八進(jìn)制、十進(jìn)制、十六進(jìn)制等)、大整數(shù)(超過32位)和浮點(diǎn)數(shù)。符號是由字母、數(shù)字、‘_’、‘.’、‘$’等組成的字符串,必須以字母開頭,大小寫有區(qū)別。符號用來給匯編程序中的對象命名,如標(biāo)號、常量等。符號有兩個屬性,分別是value和type??梢杂谩?’或“.set”改變符號的值?!?’是一個特殊的符號,表示當(dāng)前地址。表達(dá)式是由操作符和括號連接起來的一組符號和常量,其結(jié)果是一個地址或一個數(shù)值。GNU匯編的表達(dá)式與C語言表達(dá)式基本一致。

GNU的匯編指示(AssemblerDirectives)又稱偽操作(PseudoOps),是匯編程序提供給匯編器的指示或命令,用于聲明目標(biāo)文件的生成方法。所有的匯編指示都以‘.’開頭。GNU匯編提供了100多個指示,在Linux源代碼中用到的有以下幾種:

(1)

.align

abs-expr1,

abs-expr2,

abs-expr3:用第2個表達(dá)式的值填充目標(biāo)文件中的當(dāng)前節(jié),使下一個可用位置是第1表達(dá)式的倍數(shù)允許跳過的最大字節(jié)數(shù)由第3個表達(dá)式?jīng)Q定。第2、3表達(dá)式都是可選的。

(2)

.balign[wl]

abs-expr1,

abs-expr2,

abs-expr3:.balign與.align的意思相同。變體.balignw表示填充值是2字節(jié)的字,.balignl表示填充值是4字節(jié)的長字。

(3)

.p2align[wl]

abs-expr1,

abs-expr2,

abs-expr3:.p2align[wl]與.align[wl]相似,不同之處在于下一個可用位置是2

abs-expr1的倍數(shù)。

(4)

.org

new-lc,fill:從new-lc標(biāo)識的新位置開始存放下面的代碼或數(shù)據(jù),空出來的空間用fill填充。新位置是在同一節(jié)中的偏移量。

(5)

.ascii"string"...:定義一到多個字符串。字符串后不自動加0結(jié)尾。

(6)

.asciz"string"...:定義一到多個字符串。字符串后自動以0結(jié)尾。

(7)

.string

"str":將字符串拷貝到目標(biāo)文件中,串以0結(jié)尾。

(8)

.byte

expressions:定義一到多個字節(jié)類型(1字節(jié))的表達(dá)式。

(9)

.word

expressions:定義一到多個字類型(2字節(jié))的表達(dá)式。

(10)

.long

expressions:定義一到多個長整數(shù)(4字節(jié))類型的表達(dá)式。

(11)

.quad

bignums:定義一到多個8字節(jié)的長整數(shù)。

(12)

.fill

repeat,

size,

value:將value值拷貝repeat次,其中每個value占用size字節(jié)。如“.fill1024,4,0”會產(chǎn)生一個全0的頁。

(13)

.space

size,

fill和.skip

size,

fill:在目標(biāo)文件的當(dāng)前位置處留出size字節(jié)的空間,并在其中填入值fill,如未指定fill,則填入0。

(14)

.rept

count和.endr:將.rept和.endr之間的行重復(fù)count次。

(15)

.set

symbol,

expression:將符號symbol的值設(shè)為expression。

(16)

.typename,

@type:

將符號name的type屬性設(shè)為type。

其中type可以是function

(符號name是一個函數(shù)名)或object(符號name是一個數(shù)據(jù)對象)。

(17)

.sizename,expression:將符號name所占空間設(shè)為expression。

(18)

.global

symbol或.globl

symbol:使符號symbol成為全局的,即使該符號對鏈接程序是可見的。

(19)

.sectionname[,"flags"[,@type[,flag_specific_arguments]]]:切換當(dāng)前節(jié),即將下面的代碼或數(shù)據(jù)匯編到name節(jié)中。其中flags可以是a(節(jié)是可分配的)、w(節(jié)是可寫的)、x(節(jié)是可執(zhí)行的);type可以是@progbits(節(jié)中包含數(shù)據(jù))、@nobits(節(jié)中不含數(shù)據(jù),只是占位空間)、@note(節(jié)中包含注釋信息,不是程序)。

(20)

.subsection

num:切換當(dāng)前子節(jié),即將下面的代碼或數(shù)據(jù)放在由num指定的子節(jié)中,節(jié)保持不變。

(21)

.text

subsection:切換當(dāng)前子節(jié),即將下面的程序匯編到.text節(jié)的編號為subsection的子節(jié)中。如未提供subsection,其缺省值為0。

(22)

.datasubsection:切換當(dāng)前子節(jié),即將下面的數(shù)據(jù)匯編到.data節(jié)的編號為subsection的子節(jié)中。如未提供subsection,其缺省值為0。

(23)

.previous:將當(dāng)前節(jié)換回到前一個節(jié)與子節(jié),即將下面的指令或數(shù)據(jù)匯編到當(dāng)前節(jié)之前使用的節(jié)與子節(jié)中,如:

.sectionA

.subsection1

.word0x1234

.subsection2

.word0x5678 //0x5678放在subsection2中

.previous

.word0x9abc //0x1234與0x9abc放在subsection1中(24)

.code16:將下面的程序匯編成16位代碼(實(shí)模式或保護(hù)模式)。

(25)

.code32:將下面的程序匯編成32位代碼。2.4.2AT&T指令語法

與Intel指令相比,AT&T格式的指令有如下特點(diǎn):

(1)指令操作數(shù)的順序是先源后目的,與Intel指令的先目的后源的順序相反。

(2)寄存器操作數(shù)前加前綴%,立即數(shù)前加前綴$。

(3)操作碼帶后綴以指明操作數(shù)的長度。后綴有b(8位)、w(16位)、l(32位)、q(64位)。在新版本的GUN匯編中,可以不帶后綴。如:

moww%bx,%ax //將bx寄存器的內(nèi)容拷貝到ax寄存器中

movw$1,%ax //將ax寄存器的值設(shè)為常數(shù)1

movlX,%eax //將變量X的值傳遞到eax寄存器中

(4)符號常數(shù)直接引用,如:

value:.long0x12345678 //定義一個雙字類型的符號常量value

movlvalue,%ebx //ebx的值是0x12345678

引用符號常量的地址時,要在符號常量前加$,如:

movl$value,%ebx //將符號value的地址裝入ebx

(5)大部分指令的操作碼都與Intel指令相同,但也有幾個例外,如:

lcall$S,$O //長調(diào)用,Intel的表示是callfarS:O

ljmp$S,$O //長跳轉(zhuǎn),Intel的表示是jmpfarS:O

lret$V //長返回,Intel的表示是retfarV

(6)內(nèi)存間接尋址的寫法是disp(base,index,scale),其意思是地址[base+disp+index*scale],如:

movl4(%ebp),%eax //從地址[ebp+4]中取1個長字給eax

movlary(,%eax,4),%eax //從地址[4*eax+ary]中取1個長字給eax

movwary(%ebx,%eax,4),%cx //從地址[ebx+4*eax+ary]中取1個字給cx

(7)

call和jmp的操作數(shù)前可以加“*”,以表示絕對地址,未加“*”表示相對地址(相對于EIP)。如call*%edi。

(8)允許使用局部標(biāo)號(數(shù)字標(biāo)號),而且允許重復(fù)定義局部標(biāo)號。在以局部標(biāo)號為目的的轉(zhuǎn)移指令上,標(biāo)號要帶上后綴,b表示向后(已執(zhí)行過的部分),f表示向前(未執(zhí)行過的部分)。如:

1: jmp1f //跳到第3行

2: jmp1b //跳到第1行

1: jmp2f //跳到第4行

2: jmp1b //跳到第3行2.4.3GNU內(nèi)嵌匯編

GCC允許在C語言代碼中嵌入?yún)R編代碼,以實(shí)現(xiàn)C語言語法無法實(shí)現(xiàn)或不便實(shí)現(xiàn)的基礎(chǔ)操作,如讀寫系統(tǒng)寄存器等。內(nèi)嵌匯編的格式也是AT&T的,如下:

_asm__volatile_(

asmstatements

:outputs

:inputs

:registers-modified

);各部分的意義如下:

(1)

_asm_是一個宏,用于聲明一個內(nèi)嵌的匯編表達(dá)式,是必不可少的關(guān)鍵字。

(2)

_volatile_是一個宏,用于聲明“不要優(yōu)化該段內(nèi)嵌匯編,讓它們保持原樣”。_volatile_是可選的。

(3)

asmstatements部分是一組AT&T格式的匯編語句,可以為空。一般情況下,在一行上應(yīng)只寫一個匯編語句。如果需要在一行上寫多個語句,它們之間要用分號或“\n”(換行)隔開。所有的語句都要括在雙引號內(nèi),可以用一對雙引號,也可以用多對雙引號。寄存器前面要加兩個%做前綴。支持局部標(biāo)號,且可以使用數(shù)字標(biāo)號。

(4)

inputs部分指明內(nèi)嵌匯編程序的輸入?yún)?shù)。每個輸入?yún)?shù)都括在一對圓括號內(nèi),各參數(shù)間用逗號分開。每個輸入?yún)?shù)前都要加一到多個用雙引號括起來的約束標(biāo)志,用于向編譯器聲明該參數(shù)的輸入位置(寄存器)及其相關(guān)信息。

(5)

outputs部分指明內(nèi)嵌匯編程序的輸出參數(shù)。每個輸出變量都括在一對圓括號內(nèi),各個輸出參數(shù)間用逗號隔開。每個輸出參數(shù)前都要加一到多個用雙引號括起來的約束標(biāo)志,以告訴編譯器從何處輸出該參數(shù)及其相關(guān)信息。輸出約束標(biāo)志與輸入約束標(biāo)志相同,但前面還要多加一個“=”。輸出參數(shù)應(yīng)該是左值,而且必須是可寫的。如果一個參數(shù)既做輸出又做輸入,可以在其前面加入“+”約束,也可以將它分成兩個,一個寫在outputs部分為只寫參數(shù),一個寫在inputs部分為只讀參數(shù)。

(6)輸入和輸出參數(shù)從0開始統(tǒng)一編號。一個編號可以唯一標(biāo)識一個參數(shù)。在asmstatements部分可以通過標(biāo)識號(加前綴%)來引用參數(shù)。在inputs部分,可以用標(biāo)識號做輸入約束標(biāo)志(括在雙引號內(nèi)),告訴編譯器將該輸入?yún)?shù)與標(biāo)識號所標(biāo)識的輸出參數(shù)放在同一個寄存器中。

(7)

registers-modified告訴編譯器內(nèi)嵌匯編程序?qū)⒁薷牡募拇嫫?。每個寄存器都用雙引號括起來,各寄存器間用逗號隔開。如果內(nèi)嵌匯編程序中引用了某個特定的硬件寄存器,就應(yīng)該在此處列出該寄存器,以告訴編譯器這些寄存器的值被改變了。如果匯編程序中用某種不可預(yù)測的方式修改了內(nèi)存,應(yīng)該在此處加上“memory”。

內(nèi)嵌匯編中常用的約束標(biāo)志有下列幾種:

“g”:讓編譯器決定將參數(shù)裝入哪個寄存器。

“a”:將參數(shù)裝入到ax/eax,或從ax/eax輸出。

“b”:將參數(shù)裝入到bx/ebx,或從bx/ebx輸出。

“c”:將參數(shù)裝入到cx/ecx,或從cx/ecx輸出?!癲”:將參數(shù)裝入到dx/edx,或從dx/edx輸出。

“D”:將參數(shù)裝入到di/edi,或從di/edi輸出。

“S”:將參數(shù)裝入到si/esi,或從si/esi輸出。

“q”:可以將參數(shù)裝入到ax/eax、bx/ebx、cx/ecx或dx/edx寄存器中。

“r”:可以將參數(shù)裝入到任一通用寄存器中。

“i”:整型立即數(shù)。

“m”:內(nèi)存參數(shù)。

“p”:有效內(nèi)存地址。

“=”:輸出,參數(shù)是只寫的左值。

“+”:既是輸入?yún)?shù)又是輸出參數(shù)?!?”:一般情況下,GCC將把輸出參數(shù)和一個不相干的輸入?yún)?shù)分配在同一個寄存器中,因?yàn)樗僭O(shè)在輸出產(chǎn)生之前,所有的輸入都已被消耗掉了。但如果內(nèi)嵌的匯編程序有多條指令,這種假設(shè)就不再正確。在輸出參數(shù)之前加入“&”,可以保證輸出參數(shù)不會覆蓋掉輸入?yún)?shù)。此時,GCC將為該輸出參數(shù)分配一個輸入?yún)?shù)還沒有使用到的寄存器,除非特殊聲明(如用數(shù)字0~9)。

“0~9”:稱為匹配約束標(biāo)志,用于約束一個既做輸入又做輸出的參數(shù),表示輸入?yún)?shù)和輸出參數(shù)占據(jù)同一個寄存器。數(shù)字約束標(biāo)志只能出現(xiàn)在輸入?yún)?shù)中,是與其共用同一位置的輸出參數(shù)的編號。

inputs、outputs和registers-modified部分都可有可無。如有,順序不能變;如無,應(yīng)保留“:”,除非不引起二義性。

例:

#defineload_gdt(dtr)_asm__volatile("lgdt%0"::"m"(*dtr))

#defineswitch_to(prev,next,last)do{ \

unsignedlongesi,edi; \

asmvolatile( "pushl%%ebp\n\t" \

"movl%%esp,%0\n\t" /*saveESP*/ \

"movl%5,%%esp\n\t" /*restoreESP*/

\

"movl$1f,%1\n\t" /*saveEIP*/ \

"pushl%6

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論