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

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

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

在復雜的計算機硬件平臺中,最核心的是處理器,與內核設計關系最密切的也是處理器。雖然Linux內核可以運行在多種處理器之上,但Intel系列的處理器是Linux支持的第一種處理器,也是目前最常見的處理器,更是本書的討論基礎。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

圖2.2處理器操作模式之間的轉換2.2.2段頁式內存管理

IA-32體系結構提供了極為復雜的段頁式內存管理機制,即先分段,再分頁。其中段式管理是默認、必須的,頁式管理是可選的。保留段式是為了兼容,提供頁式是為了支持虛擬內存。

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

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

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

DPL是段的特權級,其值在0~3之間。

S是系統(tǒng)標志,用于區(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位表示地址擴展方向(0表示向大方向擴展,1表示向小方向擴展),第1位表示段是否可寫(0表示不能寫,1表示可寫)。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

段頁式內存管理是Intel處理器提供的最基礎的內存管理機制,在此基礎上,Intel還提供了許多變化與擴展。

(1)基本平板式內存管理。基本思路是屏蔽掉段式管理,完全采用頁式管理。做法是定義一個代碼段、一個數(shù)據(jù)段,兩個段的基地址都是0,大小都是4GB。如此以來,邏輯地址就是線性地址,段式管理的作用被屏蔽。對段內內存(實際是所有內存)的管理完全依靠分頁機制。

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

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

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

(5)

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

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

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

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

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

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

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

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

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

(6)即使使用調用門、中斷門或陷阱門,也不能從高特權級向低特權級轉移控制。

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

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

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

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

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

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

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

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

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

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

通過陷入指令也可以進入中斷或異常處理程序,但要進行特權級檢查,要求進入前的特權級(CPL)必須小于或等于門描述符的特權級(DPL)。中斷或陷阱門的DPL通常被設為0,因而用戶程序無法通過INTn指令直接進入中斷或異常處理程序。中斷或異常處理程序運行在當前進程的上下文中,但可能使用不同的堆棧。如果中斷或異常發(fā)生時處理器在第0特權級上(在執(zhí)行內核),則處理程序可直接使用當前進程的系統(tǒng)堆棧,不用切換堆棧。如果中斷或異常發(fā)生時處理器在第3特權級上(在執(zhí)行用戶程序),則需要切換堆棧,即從當前進程的用戶堆棧切換到它的系統(tǒng)堆棧。TR中記錄著當前進程的TSS,其中包含當前進程的系統(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的中斷和異常,處理程序應在棧頂壓入一個值,如Linux的外部中斷處理程序會壓入中斷向量號、無error-code的異常處理程序會壓入0或-1。圖2.10是中斷發(fā)生時堆棧的變化情況。圖2.10中斷或異常發(fā)生時的堆棧變化在64位模式中,中斷和異常的處理方式有所改變,如處理程序必須在64位代碼段中,因而中斷和陷阱門描述符被擴充到了16字節(jié),其中的偏移量被擴充到了64位;IDT中僅有新格式的門描述符;堆棧寬度變成了64位,而且當中斷發(fā)生時,會無條件地壓入棧指針(SS:RSP);當需要切換堆棧時,新的SS被強制設為NULL;新增加了的中斷堆棧表(IST)機制,允許為特定的中斷或異常指定專門的堆棧。2.2.7APIC

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

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

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

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

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

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

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

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

從P6系列開始,在IA-32體系結構中增加了一個多處理器初始化協(xié)議(MP),用于規(guī)定多處理器系統(tǒng)的初始化過程。MP協(xié)議將處理器分為自舉處理器BSP(BootstrapProcessor)和應用處理器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)拓撲結構,給系統(tǒng)總線上的每一個邏輯處理器一個唯一的8位APICID。該ID號被寫入處理器的局部APICID寄存器中。

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

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

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

(5)

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

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

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

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

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

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

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

IA-32體系結構提供了4個內存管理寄存器,分別稱為GDTR、LDTR、IDTR、TR。Intel64體系結構將它們的基地址部分擴充到了64位。

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

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

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

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

GNU的C語言是對標準C語言的擴展,其編譯器稱為GCC。GCC是RichardStallman于1984年發(fā)起的一個項目,最初的目的是開發(fā)一個免費的

C

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

GCC中的C編譯器是Linux的基礎,Linux內核源代碼只能用GCC編譯。GCC支持C語言標準C89、C90、C94、C95、C99等,并有自己的擴展,如:

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

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

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

foo(doublea,doubleb){

double square(doublez){returnz*z;}

return square(a)+square(b);

}

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

typeof(*x)y[4]; //指針數(shù)組,指針所指類型是參數(shù)x的類型

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

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

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

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

structline{

int length;

char contents[0];

};

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

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

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

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

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

(9)在定義函數(shù)時,允許用關鍵字_attribute_聲明屬性??梢月暶鞯膶傩园▽R要求(aligned)、即將過時(deprecated)、快速調用(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)在定義結構或聯(lián)合類型時,可以通過關鍵字_attribute_聲明相關的屬性。在聲明變量或結構成員時,可以通過關鍵字_attribute_聲明相關的屬性,如對齊要求、存放變量的節(jié)等。

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

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

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

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

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

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

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

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

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

.align4 //4對齊

.long1b,syscall_fault //異常處理表項,意思是當1處的指令出錯時,轉syscall_fault

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

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

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

(1)

.align

abs-expr1,

abs-expr2,

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

(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標識的新位置開始存放下面的代碼或數(shù)據(jù),空出來的空間用fill填充。新位置是在同一節(jié)中的偏移量。

(5)

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

(6)

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

(7)

.string

"str":將字符串拷貝到目標文件中,串以0結尾。

(8)

.byte

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

(9)

.word

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

(10)

.long

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

(11)

.quad

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

(12)

.fill

repeat,

size,

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

(13)

.space

size,

fill和.skip

size,

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

(14)

.rept

count和.endr:將.rept和.endr之間的行重復count次。

(15)

.set

symbol,

expression:將符號symbol的值設為expression。

(16)

.typename,

@type:

將符號name的type屬性設為type。

其中type可以是function

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

(17)

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

(18)

.global

symbol或.globl

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

(19)

.sectionname[,"flags"[,@type[,flag_specific_arguments]]]:切換當前節(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:切換當前子節(jié),即將下面的代碼或數(shù)據(jù)放在由num指定的子節(jié)中,節(jié)保持不變。

(21)

.text

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

(22)

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

(23)

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

.sectionA

.subsection1

.word0x1234

.subsection2

.word0x5678 //0x5678放在subsection2中

.previous

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

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

(25)

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

與Intel指令相比,AT&T格式的指令有如下特點:

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

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

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

moww%bx,%ax //將bx寄存器的內容拷貝到ax寄存器中

movw$1,%ax //將ax寄存器的值設為常數(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 //長調用,Intel的表示是callfarS:O

ljmp$S,$O //長跳轉,Intel的表示是jmpfarS:O

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

(6)內存間接尋址的寫法是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)允許使用局部標號(數(shù)字標號),而且允許重復定義局部標號。在以局部標號為目的的轉移指令上,標號要帶上后綴,b表示向后(已執(zhí)行過的部分),f表示向前(未執(zhí)行過的部分)。如:

1: jmp1f //跳到第3行

2: jmp1b //跳到第1行

1: jmp2f //跳到第4行

2: jmp1b //跳到第3行2.4.3GNU內嵌匯編

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

_asm__volatile_(

asmstatements

:outputs

:inputs

:registers-modified

);各部分的意義如下:

(1)

_asm_是一個宏,用于聲明一個內嵌的匯編表達式,是必不可少的關鍵字。

(2)

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

(3)

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

(4)

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

(5)

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

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

(7)

registers-modified告訴編譯器內嵌匯編程序將要修改的寄存器。每個寄存器都用雙引號括起來,各寄存器間用逗號隔開。如果內嵌匯編程序中引用了某個特定的硬件寄存器,就應該在此處列出該寄存器,以告訴編譯器這些寄存器的值被改變了。如果匯編程序中用某種不可預測的方式修改了內存,應該在此處加上“memory”。

內嵌匯編中常用的約束標志有下列幾種:

“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”:內存參數(shù)。

“p”:有效內存地址。

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

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

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

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

例:

#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)系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論