X86/X64處理器體系結構及尋址模式

由8086/8088、x86、Pentium發展到core系列短短40多年間,處理器的時鐘頻率幾乎已接近極限,儘管如此,自從86年Intel推出386至今除了增加一些有關流媒體的指令如mmx/sse之外,其他新增的大多數指令都可以從最初的指令集中組合實現同樣的功能,整個編程模型維持了約有20多年。

1. 處理器體系結構

1.1. 處理器簡要結構

我們都知道CPU的根本任務就是執行指令,對計算機來說最終都是一串由“0”和“1”組成的序列。CPU從邏輯上可以劃分成3個模塊,分別是控制單元、運算單元和存儲單元,這三部分由CPU內部總線連接起來。如下所示:

1. 控制單元:控制單元是整個CPU的指揮控制中心,由指令寄存器IR(Instruction Register)、指令譯碼器ID(Instruction Decoder)和操作控制器OC(Operation Controller)等,對協調整個電腦有序工作極爲重要。它根據用戶預先編好的程序,依次從存儲器中取出各條指令,放在指令寄存器IR中,通過指令譯碼(分析)確定應該進行什麼操作,然後通過操作控制器OC,按確定的時序,向相應的部件發出微操作控制信號。操作控制器OC中主要包括節拍脈衝發生器、控制矩陣、時鐘脈衝發生器、復位電路和啓停電路等控制邏輯。
2. 運算單元:是運算器的核心。可以執行算術運算(包括加減乘數等基本運算及其附加運算)和邏輯運算(包括移位、邏輯測試或兩個值比較)。相對控制單元而言,運算器接受控制單元的命令而進行動作,即運算單元所進行的全部操作都是由控制單元發出的控制信號來指揮的,所以它是執行部件。
3. 存儲單元:包括CPU片內緩存和寄存器組,是CPU中暫時存放數據的地方,裏面保存着那些等待處理的數據,或已經處理過的數據,CPU訪問寄存器所用的時間要比訪問內存的時間短。採用寄存器,可以減少CPU訪問內存的次數,從而提高了CPU的工作速度。但因爲受到芯片面積和集成度所限,寄存器組的容量不可能很大。寄存器組可分爲專用寄存器和通用寄存器。專用寄存器的作用是固定的,分別寄存相應的數據。而通用寄存器用途廣泛並可由程序員規定其用途,通用寄存器的數目因微處理器而異。這個是我們以後要介紹這個重點,這裏先提一下。
我們將上圖細化一下,可以得出CPU的工作原理概括如下:

這裏寫圖片描述

總的來說,CPU從內存中一條一條地取出指令和相應的數據,按指令操作碼的規定,對數據進行運算處理,直到程序執行完畢爲止。

1.2. 寄存器簡要結構

這裏寫圖片描述

這裏寫圖片描述

以上所列出的一些通用寄存器(注:其中RSP爲專用寄存器,之所以把它放在通用寄存器組中只是爲了方便記憶整個模型),除了數據位寬度不同之外,並無多大差別:

  • RAX(累加器):RAX如果是8/16/32位尋址,則只改變該寄存器的一部分。累加器用於乘法、除法及一些調整指令,同時也可以保存存儲單元的偏移地址。
  • RBX(基址):用於保存存儲單元的偏移地址,同時也能尋址存儲器數據,作爲偏移地址訪問數據時默認使用數據段基址DS作爲段前綴。
  • RCX(計數):可保存訪問存儲單元的偏移地址,或在串指令(REP/REPE/REPNE)以及移位、循環和LOOP/LOOPD指令中用作計數器。
  • RDX(數據):可使用RDX/EDX/DX/DH/DL尋址,同時作爲通用寄存器也用於保存乘法形成的部分結果或者除法之前的部分被除數,也可用於尋址存儲單元。
  • RBP(基指針):可用RBP/EBP/BP尋址,同時作爲偏移地址訪問存儲單元時默認使用堆棧段基址SS作爲段前綴。
  • RDI(目的變址):可用RDI/EDI/DI尋址,常用於在串指令中尋址目的數據串。
  • RSI(源變址):如RDI一樣,RSI也可作爲通用寄存器使用,通常爲串指令尋址源數據串。

段寄存器CS、DS、ES、SS、FS、GS以及RSP爲專用寄存器,以下是這些寄存器的概要描述:

  • RSP(堆棧指針):RSP尋址稱爲堆棧的存儲區,通過該指針存取堆棧數據。用作16位寄存器時使用SP,如果是32位則爲ESP。
  • CS(代碼段):代碼段寄存器存放程序所使用的代碼在存儲器中的基地址。 • DS(數據段):存放數據段的基地址。
  • ES(附加段):該段寄存器通常在串指令(LODS/STOS/MOVS/INS/OUTS)中使用,主要用於在存儲器中將數據進行成塊轉移。
  • SS(堆棧段):爲堆棧定義一個存儲區域。主要用來存放過程調用所需參數、本地局部變量以及處理器狀態等。
  • FS與GS:這兩個段寄存器是386~Core2中新增的段寄存器,以允許程序訪問附加的存儲器段。可以將其視爲“通用的段寄存器”,通過將段的基地址存入這兩個寄存器中可以實現自定義的尋址操作,從而增加了編程的靈活性。

每一個寄存器都有一個”可見”部分和一個”隱藏”部分。(這個隱藏部分有時也指一個”描述符緩存”(descriptor cache)或者”陰影寄存器”(shadow register))。當一個段選擇器被加載到段寄存器的可見部分,處理器也會自動把基址,段界限,和段描述符中的訪問控制信息加載到段寄存器的隱藏部分。把信息緩存在段寄存器(可見和隱藏部分)允許處理器不經過額外的總線循環(bus cycles)去段描述符總讀取基址和界限來轉換地址。當描述符表發生了更改,軟件有義務重新加載段寄存器。如果不這樣做,段寄存器中使用的老段描述符還是會繼續使用。

這裏寫圖片描述

如上圖所示,在Pentium4及更高型號處理器中增加了R8~R15這8個64位通用寄存器,這些新增的64位寄存器仍支持按字節、字、雙字或四字方式尋址,而不同之處在於只有最右邊的數據位可以用來作爲單獨的一個字節/字等。注意在使用這些新增寄存器的其中一個部分時需要在寄存器末尾添加控制字,例如:

  1. mov R11D, R8D ;其中字母D用於表示雙字訪問
  2. ;也可以將D改爲B或者W,B表示字節訪問,W表示字訪問
  3. ;如果不加任何控制字則使用整個寄存器

這裏寫圖片描述

RIP尋址代碼段中當前執行指令的下一條指令,當處理器工作在實模式下時使用16位的IP寄存器,當工作於保護模式時則使用32位的EIP。指令指針可由轉移指令或調用指令修改。需要注意的是,在64位模式中由於處理器包含40位地址總線,所以總共可以尋址240=1TB的內存

這裏寫圖片描述

EFLAGS(program status and control) register主要用於提供程序的狀態及進行相應的控制,在64-bit模式下,EFLGAS寄存器被擴展爲64位的RFLGAS寄存器,高32位被保留,而低32位則與EFLAGS寄存器相同。

32位的EFLAGS寄存器包含一組狀態標誌、系統標誌以及一個控制標誌。在x86處理器初始化之後,EFLAGS寄存器的狀態值爲0000 0002H。第1、3、5、15以及22到31位均被保留,這個寄存器中的有些標誌通過使用特殊的通用指令可以直接被修改,但並沒有指令能夠檢查或者修改整個寄存器。通過使用LAHF/SAHF/PUSHF/POPF/POPFD等指令,可以將EFLAGS寄存器的標誌位成組移到程序棧或EAX寄存器,或者從這些設施中將操作後的結果保存到EFLAGS寄存器中。在EFLAGS寄存器的內容被傳送到棧或是EAX寄存器後,可以通過位操作指令(BT, BTS, BTR, BTC)檢查或修改這些標誌位。當調用中斷或異常處理程序時,處理器將在程序棧上自動保存EFLAGS的狀態值。若在中斷或異常處理時發生任務切換,那麼EFLAGS寄存器的狀態將被保存在TSS中 【the state of the EFLAGS register is saved in the TSS for the task being suspended.】 ,注意是將要被掛起的本次任務的狀態。

EFLAGS寄存器的狀態標誌(0、2、4、6、7以及11位)指示算術指令(如ADD, SUB, MUL以及DIV指令)的結果。位於EFLAGS寄存器的第10位DF標誌(DF flag) 控制串指令(MOVS, CMPS, SCAS, LODS以及STOS)。設置DF標誌使得串指令自動遞減(從高地址向低地址方向處理字符串),清除該標誌則使得串指令自動遞增。EFLAGS寄存器中的系統標誌以及IOPL域(System Flags and IOPL Field) 用於控制操作系統或是執行操作,它們不允許被應用程序所修改。

2. 處理器工作及尋址模式

對於一根實際的、實實在在的、物理的、可看得見、摸得着的內存條而言,處理器把它當做8位一個字節的序列來管理和存取,每一個內存字節都有一個對應的地址,我們叫它物理地址,用地址可以表示的長度叫做尋址空間。而CPU是如何去訪問內存單元裏的數據的方式就叫做尋址。

2.1. 實模式

8086得CPU在內存尋址方面第一次引入了一個非常重要的概念—-段。在8086之前都是4位機和8位機的天下,那是並沒有段的概念。當程序要訪問內存時都是要給出內存的實際物理地址,這樣在程序源代碼中就會出現很多硬編碼的物理地址。段寄存器的產生源於Intel 8086 CPU體系結構中數據總線與地址總線的寬度不一致。也就是爲了實現16位8086 CPU實現20位地址總線位寬。爲了支持分段機制,Intel在8086的CPU裏新增了4個寄存器,分別是代碼段CS,數據段DS,堆棧段SS和其他ES。這樣一來,一個物理地址就由兩個部分組成,分別是“段地址”:“段內偏移量”。在實模式中,通常尋址時都是通過段寄存器+通用寄存器,即基址+變址的方式進行尋址。例如,ES=0x1000,DI=0xFFFF,那麼這個數據ES:DI在內存裏的絕對物理地址就是:

AD(Absolute Address)=(ES)*(0x10)+(DI)=0x1FFFF

就是講段基地址左移4位然後加上段內偏移量就得到了物理內存裏的絕對地址,經過這麼一個變換,就可以得到一個20位的地址,8086就可以對20位的1M內存空間進行尋址了。如下:

這裏寫圖片描述

很明顯,這種方式可以尋址的最高地址爲0xFFFF:0xFFFF,其地址空間爲0x00000~0x10FFEF,因爲8086的地址總線是20位,最大隻能訪問到1MB的物理地址空間,即物理地址空間是0x00000~0xFFFFF。當程序訪問0x100000~0x10FFEF這一段地址時,因爲其邏輯上是正常的,CPU並不會認爲其訪問越界而產生異常,但這段地址確實沒有實際的物理地址與其對應,怎麼辦?此時CPU採取的策略是,對於這部分超出1M地址空間的部分,自動將其從物理0地址處開始映射。也就是說,系統計算實際物理地址時是按照對1M求模運算的方式進行的,在有些技術文獻裏你會看到這種技術被稱之爲wrap-around。還是通過一幅圖來描述一下吧:
這裏寫圖片描述

根據前面的講解我們可以發現段基址有個特徵,其低4位全爲0,也就是說每個段的起始地址一定是16的整數倍,這是分段的一個基本原則。這樣每個段的最小長度是16字節,而最大長度只能是64KB。這裏我們可以計算一下,1MB的物理地址空間能劃分成多少個段。

如果每個段的長度爲16字節,這樣1MB物理地址空間最多可以劃分成64K個段;

如果每個段的長度爲64KB,那麼1MB的物理地址空間最多能劃分成16個段。

8086這種分段基址雖然實現了尋址空間的提升,但是也帶來一些問題:

  • 同一個物理地址可以有多種表示方法。例如0x01C0:0x0000和0x0000:0x1C00所表示的物理地址都是0x01C00。
  • 地址空間缺乏保護機制。對於每一個由段寄存器的內容確定的“基地址”,一個進程總是能夠訪問從此開始64KB的連續地址空間,而無法加以限制。另一方面,可以用來改變段寄存器內容的指令也不是什麼“特權指令”,也就是說,通過改變段寄存器的內容,一個進程可以隨心所欲地訪問內存中的任何一個單元,而絲毫不受限制。不能對一個進程的內存訪問加以限制,也就談不上對其他進程以及系統本身的保護。與此相應,一個CPU如果缺乏對內存訪問的限制,或者說保護,就談不上什麼內存管理,也就談不上是現代意義上的中央處理器。

8086和後來的80186,這種只能訪問1MB地址空間的工作模式,我們將其稱之爲“實模式”。我的理解就是“實際地址模式”,因爲通過段基址和段偏移算出來的地址,經過模1MB之後得出來的地址都是實際內存的物理地址。

雖然現在CPU已經發展到了64位的酷睿6代,但是仍然保持着實模式這個工作模式。CPU的實模式是爲了與8086處理器兼容而設置的。在實模式下,CPU處理器就相當於一個快速的8086處理器。CPU處理器被複位或加電的時候以實模式啓動。這時候處理器中的各寄存器以實模式的初始化值工作。CPU處理器在實模式下的存儲器尋址方式和8086基本一致,由段寄存器的內容乘以16作爲基地址,加上段內的偏移地址形成最終的物理地址,這時候它的32位地址線只使用了低20位,即可訪問1MB的物理地址空間。在實模式下,CPU處理器不能對內存進行分頁機制的管理,所以指令尋址的地址就是內存中實際的物理地址。在實模式下,所有的段都是可以讀、寫和執行的。實模式下CPU不支持優先級,所有的指令相當於工作在特權級(即優先級0),所以它可以執行所有特權指令,包括讀寫控制寄存器CR0等。這實際上使得在實模式下不太可能設計一個有保護能力的操作系統。實模式下不支持硬件上的多任務切換。實模式下的中斷處理方式和8086處理器相同,也用中斷向量表來定位中斷服務程序地址。中斷向量表的結構也和8086處理器一樣,每4個字節組成一箇中斷向量,其中包括兩個字節的段地址和兩個字節的偏移地址。應用程序可以任意修改中斷向量表的內容,使得計算機系統容易受到病毒、木馬等的攻擊,整個計算機系統的安全性無法得到保證。

2.2. 保護模式(IA-32模式)

由於8086的上述問題,1982年,Intel在80286的CPU裏,首次引入的地址保護的概念。也就是說80286的CPU能夠對內存及一些其他外圍設備做硬件級的保護設置(實質上就是屏蔽一些地址的訪問)。自從最初的x86微處理器規格以後,它對程序開發完全向下兼容,80286芯片被製作成啓動時繼承了以前版本芯片的特性,工作在實模式下,在這種模式下實際上是關閉了新的保護功能特性,因此能使以往的軟件繼續工作在新的芯片下。後續的x86處理器都是在計算機加電啓動時都是工作在實模式下。

也就是說,在保護模式下,程序不能再隨意的訪問物理內存了,有些內存地址CPU做了明確的保護限制。在這些要求下,286時代的“根據段寄存器確定段基址”方法已經行不通了,我們需要的不僅僅是基址,還需要訪問權限等額外的信息,而且我們不想把具體的基址暴露給用戶。

2.2.1. 段描述符

爲了解決這些問題,intel引入一箇中間結構體,段描述符。並增設了兩個寄存器:GDTR (global descriptor talbe register)指向全局段描述符數組(表);LDTR (localdescriptor table register)執行局部段描述符數組(表)。而6個段寄存器,CS/DS/SS/ES包括後來的FS/GS,其內容不在用作基址,而是用作索引去段描述符數組中查找對應的段描述符。段描述符佔8個字節,其定義以及其中各個標誌位的定義如下:

這裏寫圖片描述

這裏寫圖片描述

段限制字段(Segment limit field) 確定段的大小。處理器將兩個段限制字段放在一起形成一個20-bit的值。根據G(粒度(granularity))標記位設置的不同,處理器有兩種方式解析段限制。

  • 如果G標記位被清除了,段大小範圍從1 byte到1 MByte,步長爲一個字節。
  • 如果G字段設置了,段大小範圍從4 KBytes到4GBytes,步長爲4-KByte。

處理器有兩種方式使用段限制,取決於段是向上擴展段(expand-up
segment)還是向下擴展段(expand-down
segment)。對於向上擴展段,邏輯地址的偏移量範圍從0到段大小限制。大於段限制的偏移量會產生一個通用保護異常(GP,對於除了SS之外的段)或者棧錯誤異常(stack-fault
exception)(SS,對於SS段)。對於向下擴展段,段限制有一個反向函數;偏移量範圍從段限制加上1到加上FFFFFFFFH或者FFFFH,取決於B標記位的設置。小於或者等於段限制的偏移量會產生一個通用保護異常或者棧錯誤異常。在向下擴展段中申請新的內存的時候,會減少段限制字段的值,並且新申請的空間在段地址空間的底部而不是頂部。IA-32
架構棧總是向下增長的,使這個機制對於擴展段來說非常便利。

基址字段(Base address fields) 在4-GByte線性地址空間中定義了段基址byte0的位置。處理器將三個基址字段加在一起形成一個32-bit的值。段基址需要在16-byte的邊界對齊。對染16-byte對齊並不是必須的,但是在16-byte邊界對齊的代碼和數據的程序有最佳表現。

類型字段(Type field) 指定了段和門的類型並且指定了段的訪問方式以及段數據增長方向。對這個字段的解釋取決於描述符類型是應用(代碼和數據)描述符或是系統描述符。類型字段的編碼在代碼,數據,和系統描述符中是不同的。

S(描述符類型(descriptor type))標識 決定了這個段描述符是一個系統段(S標記位清0)或是一個代碼或者數據段(S標記位設置了)。

DPL(描述符優先級(descriptor privilege level))字段 決定了段的特權級別。特權級別範圍從0到3,其中0是最大特權級別。DPL用來控制對段的訪問的。

P(segment-present) flag 決定了是否這個段現在是在內存(set)還是不在(clear)。如果標記位爲clear,如果有段選擇器指向段描述符加載到段寄存器的時候,處理器將會產生一個段不在異常(segment-not-present exception)(NP)。內存管理軟件使用這個標記位來控制當前時間哪些段真正的加載到物理內存。它在分頁虛擬內存之外提供了一個額外的控制。

下圖展示了當segment-present是clear狀態時,段描述符的格式。當這個標記位是clear,操作系統或者執行指令可以直接使用標記爲”可用(Avilable)”的位置來存儲自己的數據,比如缺失段下落的信息。

D/B(操作的默認大小/棧指針大小和/或上界)標記位 在段描述符是一個可執行代碼段,一個向下擴展段,或是棧段不同情況下,表現出不同的方法。(在32-bit代碼和數據段中,應該總是被設置成1,而在16-bit代碼和數據段中總是0);

  • 可執行代碼段(Executable code segment)
    此時標記位也稱爲D標記,它決定了段中有效地址和指針運算符的默認大小。如果標記位是set,認定是一個32-bit地址和32-bit或者8-bit的運算符;如果是clear,認定是一個16-bit的地址和16-bit或者8-bit運算符。
  • 棧段(Stack segment)(通過SS寄存器指向數據段)
    此時標記也稱爲B(big)標記,它決定了在隱式棧操作使用中棧指針的大小(比如pushes,pops,和calls)。如果標記爲set,會使用一個存儲在32-bit
    ESP寄存器的32-bit棧指針;如果標記是clear,會是歐諾個一個存儲在16-bit的SP寄存器的16-bit棧指針。如果棧段被設置成向下擴展的數據段,B標記也指定了棧段的上界。
  • 向下擴展數據段(Expand-down data segment)
    此時標記也稱爲B標記,它指定了段的上界。如果標記是set,上界就是FFFFFFFFH(4
    GBytes),否則(clear)就是FFFFH(64 KBytes)。
    這裏寫圖片描述

G(粒度(grandularity))標記位 決定段限制的縮放比例。如果粒度標記是clear,段限制是以字節爲單元的;如果是set,段限制是以4-KByte爲單元的。(這個標記並不影響基址的粒度。)當粒度標記位是set的,檢查偏移量有沒有超過段限制時,不會測試一個偏移量中最不重要12個位(the twelve least significant bits of an offset are not tested when checking the offset against the segment limit. )。比如,當粒度標記是set的,段限制爲0意味着有效偏移量是0到4095。

L(64-bit 代碼段)標記 在IA-32e模式,段描述符第二個雙字(doubleword)中的bit21決定了一個代碼段是否包含了原生64-bit代碼。值爲1決定了代碼段中的指令按照64-bit模式執行。值爲0決定了代碼段中的執行按照兼容模式執行。如果L-bit是set的,D-bit必須是clear的。當不在IA-32e模式或者非代碼段,bit21是保留的,而且必須是0。

可用與保留位(Available and reserved bits) 段描述符的第二個雙字的bit20對系統軟件是可用的。

通過段描述符,我們能夠得到如下信息:

  1. 段的基址,由B31-B24/B23-B16/B15-B0構成,一共32位,基址可以是4GB空間中任意地址;
  2. 段的長度,由L19-L16/L15-L0構成,一共20位。如果G位爲0,表示段的長度單位爲字節,則段的最大長度是1M,如果G位爲1,表示段的長度單位爲4kb,則段的最大長度爲1M*4K=4G。假定我們把段的基地址設置爲0,而將段的長度設置爲4G,這樣便構成了一個從0地址開始,覆蓋整個4G空間的段。訪存指令中給出的“邏輯地址”,就是放到地址總線上的“物理地址”,這有別於“段基址加偏移”構成的“層次式”地址(其實應該算作“層次式”地址的特例),所以intel稱其爲flat地址即平面地址。
  3. 段的類型,代碼段還是數據段,可讀還是可寫

描述符表存儲在由操作系統維護着的特殊數據結構中,並且由處理器的內存管理硬件來引用。這些特殊結構應該保存在僅由操作系統軟件訪問的受保護的內存區域中,以防止應用程序修改其中的地址轉換信息。同時,爲了避免每次訪問內存時都通過段寄存器去查表、去讀和解碼一個段描述符,每次更改段寄存器的內容時,CPU將段寄存器指向的段描述符中的段基址、長度以及訪問控制信息等加載到CPU中的“影子結構”中緩存起來。後續對該段的訪問控制都通過“影子結構體”來進行。

但是如果可以修改GDTR和LDTR的內容呢?我們不就可以隨便指定GDTR到我們自己僞造的段描述數組從而掌控程序嗎?爲了解決這個問題,intel將訪問這兩個寄存器的專門指令設爲特權指令(LGDT/LLDT,SGDT/SLDT),這些指令只有當CPU處於系統狀態(即在操作系統內核中)才能使用,用戶空間無法訪問寄存器的內容。這樣一來,工作1-2就完成了。

2.2.1.1. 段描述符實例

以下是一個典型的代碼段描述符:

這裏寫圖片描述

  • 基地址域的數據位寬度爲16+8+8=32,該域指示存儲器段的起始位置。
  • 20位的界限域指示段的最大偏移量,通常與描述符中的特徵位(G位,也稱爲粒度位)一起使用,當G置位時,將在20位的界限域的尾部添加FFFH形成一個32位的值。
  • AV位指示段是否有效,當AV=1時指示當前存儲器段有效,反之則無效,該位由操作系統使用,但Linux系統通常將其所省略。
  • 偏移量的數據位寬度爲32時D位被置位,爲16時該位被清零。

下圖詳細解釋了訪問權限域的各個位:

這裏寫圖片描述

下表爲Linux內核對段描述符的典型設置方式:

基地址 G 界限 S TYPE DPL D P
用戶代碼段 0x0000 0000 1 0xF FFFF 1 10(0x1010) 3 1
用戶數據段 0x0000 0000 1 0xF FFFF 1 2(0x0010) 3 1
內核代碼段 0x0000 0000 1 0xF FFFF 1 10(0x1010) 0 1
內核數據段 0x0000 0000 1 0xF FFFF 1 2(0x0010) 0 1

上述設置分別與Linux內核中的宏__USER_CS,__USER_DS,__KERNEL_CS,__KERNEL_DS相對應。

  • 4個段的基地址均被設置爲0,這意味着在Linux下邏輯地址即爲線性地址。
  • 界限爲0xF FFFF且粒度位G被置位爲1,因此所有段的大小最多可達4GB。
  • D位置位,所以偏移量的數據位寬度爲32。
  • P位被置位爲1,指示所有段的基地址和界限域均是有效的。
  • S位被置位,指示該描述符爲代碼段或數據段描述符。
  • DPL域指示段的優先級,上述設置方式表示將最高優先級00分配給內核代碼/數據段,而將最低優先級11分配給用戶代碼/數據段。
  • 根據TYPE域中的E位指示當前段爲代碼段或是數據段。在用戶/內核代碼段描述符中,C=0表示忽視描述符優先級,R=1表示當前段可讀,A=0表示當前段尚未被訪問。相應地,在用戶/內核數據段描述符中,ED=0表示該段將向上擴展,W=1表示數據可寫入,A=0同樣表示當前段尚未被訪問過。

2.2.2. 段選擇符

16位段寄存器中的內容,稱之爲段選擇符,除了高13位用作段描述符數組的索引外(因此理論上段描述符數組最多可以8192個元素),低3位有其他的用途,如下所示:


段寄存器

Index (Bits3 through 15) – 從GDT或者LDT中的8192個描述符中選擇一個。處理器將index的值乘以8(段描述符中的字節數),然後加上GDT或者LDT的基址(各自從GDTR或者LDTR寄存器)。

由於有兩個描述符數組,所以TI(Table Index)位用來確定從哪個數組中索引。

在前面的段描述符結構中,我們看到了特權級別字段(DPL),爲什麼還需要在這裏設置一個特權字段(RPL)呢?

intel的CPU有四種特權級別,0級最高,3級最低。每條指令都有其適用級別,如前述的LGDT指令要求0級特權,通常用戶的應用程序都是3級。Linux/windows中對CPU特權進行了簡化,只區分用戶級別和系統級別,分別對應3級和0級,這是後話。一般應用程序的當前級別由其代碼段的局部段描述符(即用段寄存器CS索引LDTR指向的局部描述符項)中的dpl(descriptor privilege level)決定,當然,每個段描述符的dpl都是在0級狀態下由內核設定的。而全局段描述符中的dpl有所不同,它表示所需的級別。段選擇符中的rpl也表示請求級別。這樣,當我們需要改變某個段寄存器(比如數據段DS)中的內容(段選擇符)來訪問一款新段空間時,CPU要做權限檢查:

  • 當前程序有權訪問新的段嗎?比較當前程序的當前級別與新段描述符中的dpl
  • 新的段選擇符有權訪索引新的段嗎?比較新的段選擇符中的rpl與新段描述符的dpl。

當然,具體的權限檢查比這要複雜,設計到段描述符中C位的取值,詳情情況請參考其他資料。

至此,工作1-3都完成了,保護模式已經建立了,我們來看看當訪存指令給出“邏輯地址”時,CPU如何將其轉換爲“物理地址”送往地址總線:

  • 根據指令性質確定該使用哪個段寄存器,如跳轉指令則目標地址在代碼段CS,取數據的指令目標地址在數據段;
  • 根據段寄存器的內容找到對應的段描述符。其實這一步不用找,前面介紹過了,段寄存器對應的段描述符已經在CPU的“影子結構”中了。
  • 從段描述符中獲得基址
  • 將指令中的“邏輯地址”與段的長度比較,確定是否越界
  • 根據指令的性質和段描述符中的訪問權限確定是否越權
  • 將指令中的“邏輯地址”作爲位移,與基地址相加得到實際的“物理地址”

GDT的第一個實體不是處理器使用的。一個指向GDT第一個實體的段選擇器(意思是說,一個index是0,且TI標記爲0的段選擇器)是作爲一個”空段選擇器”(null segment selector)。當一個帶有空選擇器的段寄存器(除了CS和SS之外)被加載了,處理器並不會產生異常。但是當一個帶有空選擇器的段寄存器被用來訪問內存的時候,會產生異常。一個空選擇器可以用來初始化未使用的段寄存器。當CS或者SS寄存器帶有空段選擇器時,會產生一個通用保護異常(general-protection exception)(GP)。

2.2.3. 代碼和數據段描述符類型

當段描述符中S(段描述符(descriptor type))標記是設置的表示這個描述符是一個數據或者代碼段的描述符。類型字段中的最高順序位(描述符第二個雙字的bit11)決定了這個段是一個數據段(clear)還是一個代碼段(set)。
當是一個數據段,類型字段的三個低位(bits 8,9 and 10)視爲訪問(accessed)(A),可寫(write-enable)(W),和擴展方向(expansion-direction)(E)的。查看錶3-1,關於代碼和數據段總類型字段的編碼。數據段只能是可讀或者讀寫段,由write-enable位決定。

這裏寫圖片描述

棧段是必須可讀寫的數據段。將一個不可寫的數據段加載到SS寄存器將會產生一個通用保護異常(GP)。如果棧段的大小需要動態改變,棧段需要是一個向下擴展的數據段(expansion-direction)標記是set的)。這裏,動態改變段限制將會使棧空間被加在棧的底部。如果想要靜態的棧段空間大小,棧寄存器可能是向上擴展類型或者向下擴展類型。

訪問位(accessed bit)決定了這個段定義了從上次操作系統或者執行指令clear這個位之後,這個段是否是可以訪問的。無論何時處理器將段選擇符加載到段寄存器,它都會設置這個位,假定包含這個段描述符的內存支持處理器寫。這個位會保持set狀態直到顯式的clear。這個位可以在虛擬內存管理中使用和在debugging中使用。

對於代碼段,類型字段中三個低順序位分別意味着可訪問(accessed)(A),可讀(read enable)(R),和一致(conforming)(C)。代碼段可以使只執行(execute-only)或者可執行/讀取(execute/read),取決於可讀位的設置。一個可執行/讀取段可能用在常量和靜態數據都已經被放置在指令代碼的ROM中。數據可能通過使用重寫前綴的CS指令或者在數據段寄存器(DS,ES,FS or GS寄存器)中加代碼段的段選擇器來讀取。在保護模式下,代碼段是不可寫的。

代碼段可以是一致的(conforming)或者是非一致的(nonconforming)。將執行程序轉換成更加特權的(more-privileged)一致段允許代碼繼續在當前特權級別執行。在不同特權級別的非一致段中進行轉換會導致一個通用保護異常(GP),除非使用一個調用門或者任務門。不訪問受保護功能和處理某些類型的異常(比如,除異常或者溢出)的系統工具可能被加載在一致代碼段中。需要避免被從低特權等級的程序和過程中執行的系統工具需要被放置在非一致代碼段。

注意

  • 無論觸發段是一致的還是非一致的,執行過程不能通過一個調用(call)或者一個跳轉(jump)到一個低特權的(特權等級的數字更大)代碼段。試圖進行這樣的操作會導致一個通用保護異常。
  • 所有的數據段都是非一致的,意味着它們無法在低特權的程序或者過程中被訪問。然而,不同於代碼段,數據段可以不經過使用一個特殊的訪問門被更高特權程序或者過程訪問。如果GDT或者LDT的段描述符被放置在ROM中,如果軟件或者處理器想要更新(寫入)基於ROM(ROM-based)段描述符,處理器會進入一個模糊的循環。設置所有ROM中的段描述符的可訪問位可以避免這個問題。當然,移除試圖修改ROM中段描述符的操作系統或者可執行代碼(也可以避免這個問題)。

2.2.4. 系統描述符類型

當段描述符中的S(描述符類型(descriptor type))標誌位是clear,描述符類型是系統描述符。處理器能識別下列類型的系統描述符:

  • 本地描述符-表(local descriptor-table)(LDT)段描述符
  • 任務狀態段(task-state segment)(TSS)描述符
  • 門調用(call-gate)描述符
  • 中斷門(interrupt-gate)描述符
  • 陷阱門(trap-gate)描述符
  • 任務門(task-gate)描述符

這些描述符類型分爲兩類:系統段(system-segment)描述符和門(gate)描述符gate。系統段描述符指向系統段(LDT和TSS段)。門描述符分爲放置有指向代碼段中的過程實體的門(call,interrupt, and trap gates)或者放置TSS的段選擇器的門(task gates)。

表3-2顯示的是系統段描述符和門描述符中的類型字段的編碼。注意在IA-32e模式下,系統描述符是16字節的而不是8字節。

這裏寫圖片描述

2.2.5. 段描述符表

一個段描述符表是一個段描述符的數組(參見圖3-10)。一個描述表的長度是可變的,並且最多可以容納8192(2^13)個8-byte的描述符。有下面兩種描述符表:

  • 全局描述符表(the global descriptor table)(GDT)
  • 本地描述附表(the local descriptor tables)(LDT)

    global and local descriptor tables

每一個系統必須定義一個GDT提供給系統裏面所有的程序和任務。定義一個或者多個LDT是可選項。比如,可以給每一個正在運行的獨立的任務定義一個LDT,或者一些或者所有任務共享一個相同的LDT。

GDT本身不是段;相反的,它是一個線性地址空間裏面的數據結構。GDT的線性地址的基址和限制必須被加載到GDTR寄存器中。GDT的基址必須是8字節邊界對齊的,以滿足處理器的最佳表現。GDT的限制值是通過字節描述的。和段一樣,通過將限制值和基址相加可以得到最後一個地址的有效字節。限制值爲0實際上意味着一個有效字節。因爲段描述符總是8字節長的,GDT限制值應該總是比8得倍數少1(也就是說,8N-1)。

GDT中的第一個描述符不是給處理器使用的。當把一個指向這個”空描述符(null descriptor)”的段選擇器加載到數據段寄存器(DS,ES,FS, or GS)並不會產生異常。但是試圖使用這個描述符來訪問內存的時候,會產生一個通用保護異常(GP)。通過使用這個段選擇器初始化段寄存器,意外引用到未使用的段寄存器,會保證產生一個異常。
LDT是位於LDT類型的系統段。GDT必須包含一個LDT段的段描述符。如果系統支持多LDTs,每一個必須在GDT總有一個分隔的段選擇器和段描述符。GDT可以位於LDT段描述符的任意位置。

一個LDT和他的段選擇器是可訪問的。爲了排除在LDT中訪問時的地址轉換,LDT的段選擇器,基本線性地址址,限制,和訪問權限存儲在LDTR寄存器中。
當(將GDT)在GDTR中store時(使用SGDT指令),一個48-bit”僞描述符(pseudo-descriptor)”保存在內存中(參見圖3-11)。爲了避免對齊用戶模式(特權等級3)下的對齊檢查錯誤,加的描述符需要位於偶數位的字地址(就是說,address MOD 4 = 2)。這使得處理器存儲一個對齊的字,緊接着是一個對齊的雙字。用戶模式程序經常不存儲僞描述符,但是通過這種方式對齊僞描述符可以避免產生一個對齊檢查錯誤異常。當使用SIDT指令store IDTR寄存器時,也需要相同的對齊。當store LDTR或者任務寄存器(各自使用SLDT或者STR指令),僞描述符應該存放在雙字地址(就是說,address MOD 4 = 0)。

這裏寫圖片描述

2.2.5.1. 段寄存器及段描述符表寄存器

在保護模式下使用32位通用寄存器,因而可供尋址的物理內存多達232=4GB。並且此時處理器對段寄存器的使用方式也發生了改變,段寄存器不再被解釋爲段的基地址,而是將該寄存器的16個位分成3個用於不同功能的域:

這裏寫圖片描述

如上圖所示,第3~15位存放選擇子(Selector),用於索引描述符表內的一個描述符,該描述符用於描述存儲器段的位置、長度和訪問權限。並且在TI=0時選擇全局描述符表(Global Descriptor Table, GDT),TI=1時選擇局部描述符表(Local Descriptor Table, LDT)。其中全局描述符表包含所有進程的段定義,而局部描述符表則通常由某個指定的進程所使用。因爲段選擇子爲13位,所以總共可以在一個全局/局部描述符表中索引出8192(213=8192)項,而每個描述符的大小爲8個字節,因此每個全局/局部描述符表佔用64KB內存空間。通常情況下操作系統並不爲應用創建LDT,除非應用程序顯示要求這麼做,並且所有的進程均共享同一個GDT,這就意味着默認情況下整個系統的分段結構只由一個GDT指示。此外上述段描述符表的基地址被存放在一組專用寄存器中,這些專用寄存器被稱爲段描述符表寄存器:
這裏寫圖片描述

如圖所示,TR中包含的選擇子用於從任務的描述符表中索引出一個描述符,從而在多任務系統中實現上下文切換操作,LDTR寄存器中包含的選擇子則用於從局部描述符表中索引出一個描述符。另外的GDTR與IDTR寄存器包含基地址及界限域,其中界限域的數據位寬度爲16,基地址域的數據位寬度爲32。在進入保護模式之前,必須先初始化中斷描述符表,此後在保護模式下,全局描述符表的基地址及其界限才被裝入GDTR中。

  • 尋址全局描述符時,首先根據GDTR得到全局描述符表的基地址,之後通過段寄存器中的13位段選擇子索引出其中的一個描述符。
  • 在尋址局部描述符之前,操作系統會在全局描述符表中爲某個具體應用的局部描述符表進行註冊。此後若段寄存器中的TI域被置位,則通過GDTR中的基地址域及LDTR中的段選擇子從全局描述符表中找到對應的描述符,該描述符包含了局部描述符的基地址,界限及訪問權限等,接着根據段寄存器中的13位段選擇子在局部描述符表中索引出相應的局部描述符表項,圖解如下:
    這裏寫圖片描述

另外段寄存器中的RPL域指示對存儲器段的請求優先級。因爲該域數據位的寬度爲2,所以總共有4種可以使用的優先級。但Windows/Linux均只使用其中的兩種,且將優先級00賦予內核和驅動,而將優先級11分配給應用程序。優先級從環0~環3逐漸降低,注意只有請求優先級(RPL)等於或高於段描述符中訪問權限域的優先級(DPL)才允許訪問,否則系統將指示應用程序違例。

2.2.5.2. IA-32e模式下的段描述符表

在IA-32e 模式,一個段描述符表可以包含最多8192(2^13)個8-byte描述符。段描述符表總的每一個實體可以是8字節的。系統描述符擴展成16字節(擁有兩倍的實體空間)。

GDTR和LDTR寄存器被擴展了以存放64-bit基址。相應的僞描述符是80位的。 下列的系統描述符被擴展成16字節:

  • 調用門(call gate)描述符
  • IDT門(IDT gate)描述符
  • LDT和TSS描述符

2.3. 長模式(IA-32e模式)

目前的CPU大多是支持X86-64技術的兼容CPU,這包括AMD64以及Intel的IA32E(後被正式命名爲EM64T,Extended Memory 64 Technology),因爲AMD64先出,而EM64T與AMD64完全兼容,所以也統一稱爲AMD64技術。由於AMD64技術向下兼容,所以很好的承接了以前的16位、32位資源,與此相應,X86-64兼容CPU可以運行在多種模式之下,除了熟悉的實模式,保護模式,還有長模式(Long mode)等,在長模式下,處理器完全執行64位指令,使用64位地址空間(物理內存的尋址能力卻沒有被完全擴展到64位,因爲目前的衆多CPU在其壽命期限之內都沒有機會見識到如此巨大的內存)和64操作數。因此,爲了降低製造成本,目前的CPU被限制在略少於64位尋址。注意,當前的這些限制可以(也極有可能)隨着未來新型CPU微架構的發佈而改變。結果就是,如果物理內存容量受限,即使開啓全部的64位虛擬地址空間也沒有用。後者因此被加以限制來節省成本。具體來說,CPU中可以節省成本的地方有讀取/存儲單元、緩衝存儲器大小和MMU和TLB的複雜程度。
當處於長模式(Long mode)時,64位應用程序(或者是操作系統)可以使用64位指令和寄存器,而32位和16位程序將以一種兼容子模式運行。x86-32架構的cpu,從很早的版本開始就支持“物理地址擴展”(PAE),該技術通過內存分頁機制將應用程序使用的32位地址映射到36位或52位。同樣,x86-64的cpu會做一個從64位線性地址到64位物理的映射,之後檢查這個64位物理地址的63到52位是否全0或全1,並取該地址的51到0位作爲實際的物理地址。因此:

  • 就目前的cpu來說,無論工作在長模式下,還是32位保護模式下,尋址能力都是52位。但是,因爲線性地址從32位提高到了64位,單個程序能夠使用的內存量變多了。實際上,32位windows上的程序只能使用2gb內存,而64位windows的64位程序可用的內存量實際上是無限的。
  • 長模式下cpu屏蔽了段機制,簡化了應用程序的內存管理,提高了單個寄存器的運算位數,並引入了一系列的新指令集和前綴(比如rex),使得合理優化過的64位程序比32位程序效率要高一些。
  • 長模式下引入了rip相對尋址機制,使得“位置無關代碼”的實現更容易而且更快。

不過,因爲64位windows下也要兼容32位程序,所以windows不得不維持兩份相關代碼,這就是wow64的來歷,wow64會多佔用一些資源。

還有一點是,64位下的兼容模式不再支持16位程序,所以運行16位程序需要額外的軟件,比如dosbox。

2.3.1. x64下的物理資源及系統數據結構

2.3.1.1. segment registers

x64 體系在硬件級上最大限度地削弱了 segmentation 段式管理。採用平坦內存管理模式,因此體現出來的思想是 base 爲 0、limit 忽略。但是,x64 還是對 segmentation 提供了某種程度上的支持。體現在 FS 與 GS 的與衆不同。segment registers 的 selector 與原來的 x86 下意義不變。
在 64 bit 模式下:
(1)code register(CS)

  • CS.base = 0(強制爲 0,實際上等於無效)
  • CS.limit = invalid
  • attribute:僅 CS.L 、CS.D、CS.P、CS.C 以及 CS.DPL 屬性是有效的。

注意:64 bit 模式下的 code segment descriptors 中的 L 位、D 位、P 位、C 位以及 DPL 域是有效的。code segment descriptor 加載到 CS 後僅 CS.L 、CS.D、CS.P、CS.C 以及 CS.DPL 屬性是有效的。
在 compatibility 模式下 code segment descriptor 和 CS 寄存器與原來 x86 意義相同。

(2)data registers (DS、ES 以及 SS)

  • DS.base = 0(強制爲 0,實際上等於無效)
  • DS.limit = invalid
  • DS.attribute = invalid:所有的屬性域都是無效的。

data registers 的所有域都是無效的。data segment 的 attribute 是無效的,那麼也包括 DPL、D/B 屬性。
在 64 bit 模式下,所有的 data segment 都具有 readable/writable 屬性,processor 對 data segment 的訪問不進行權限 check 以及 limit 檢查。

(3)FS 與 GS

  • FS.base 是完整是的 64 位。
  • FS.limit = invalid
  • FS.attribute = invalid

與其它 data registers 不同的是,FS 與 GS 的 base 是有效的。支持完整的 64 位地址。但是 limit 和 attribute 依舊無效的。

1、爲 FS 和 GS 加載非 0 的 64 位 base 值,使用以下指令:
  

  mov fs, ax

  pop fs

注意:這條指令只能爲 fs 提供 32 位的 base 值,這根本的原因是:data segment descriptor 提供的 base 是 32 位值。在 x64 裏的 segment descriptor 是 8 個字節。也就是 base 是 4 個字節。通過 selector 加載 base 值,只能獲取 32 位地址值。

2、爲 FS 和 GS提供 64 位地址值,可以使用以下指令:

  mov ecx, C0000100 /* FS.base msr 地址 */
  mov edx, FFFFF800
  mov eax, 0F801000
  wrmsr /* 寫 FS.base */


  上面代碼爲 FS.base 提供 0xFFFFF8000F801000 地址。

  mov ecx, C0000101 /* GS.base msr 地址 */
  mov edx, FFFFF800
  mov eax, 0F801000
  wrmsr /* 寫 GS.base */


  上面代碼爲 GS.base 提供 0xFFFFF8000F801000 地址。
  另一種方法是使用 swapgs 指令,這條指令將 kernelGS 地址與 GS.base 交換。

2.3.1.2. descriptors 結構

x64 體系已經不提供對 segmentation 的支持(或者說最大程度削弱了),對於 user segment descriptor 來說,還是停留在 x86 的階段,絕大部分的功能已經去掉。但是對於 system descriptor 來說,它是被擴展爲 16 個字節,是 128 位的數據結構。因此,descriptors 結構要分兩部分來看。

1) user segment descriptors

在 long mode 下對 user segment descriptor 有兩種解釋結果:

  • 64 位模式下的 descriptor
  • compatibility 模式下的 descriptor

在 compatibility 模式下 code segment descriptor 與 legacy x86 的 code segment descriptor 在意義在只有一點差異,在 legacy x86 模式下不存在 L 屬性,這個 L 位在 legacy x86 模式下是 0 值。而 compatibility 模式下的 L 屬性也是 0 值。實際上它們是相等的。
下面是在 64 位模式下的解釋:

user segment descriptor

它們的 segment descriptor 的 S = 1 指示它們是 user segment descriptor。上圖灰色部分的 limit 和 base在 user segment descriptor 裏是無效被忽略的,有部分屬性是支持的。然而 attribute 部分對於 Code segment descriptor 和 Data segment descriptor 有着不同的表現,粉紅色部分在 code segmnt descriptor 裏是有效的,在 data segment descriptor 裏是無效的。

a) Code segment descriptor

上圖中的白色部分和紅色部分在 code segment descriptor 裏是有效的,它們是:

  • C(Conforming):指示 code segment 是 conforming 還是 non-conforming
    類型,它們在權限控制上的表現是不一樣的。
  • DPL(Descriptor Privilige Level): 指示訪問 code segment 需要的權限
  • P(Present):指示 code segment 是否加載在內存中
  • L(Long):指示 code segment 是 64 位模式代碼還是 compatibility 模式代碼
  • D(Default operand size):指示 code segment 的 default operand size

這些 attribute 位加載到 CS 寄存器後,在 CS 寄存器的 attribute 裏同樣是有效的。雖然 x64 體系非常想拋棄 segmentation 機制,但是爲了整個 x86 架構的兼容性不得以而爲之:

  • C 和 DPL 爲權限控制和轉移而保留
  • L 和 D 爲 processor 模式和指令操作數而設
  • P 恐怕是最沒有異議

圖中綠色部分比較特別:

  • S(system) 標誌
  • C/D(Code/Data)標誌

雖然這兩個標誌是無效的,但是您必須爲它設置初始值,在設置初始值後你不能進行更改,這是無效的一面。對於 Code segment descriptor 來說,它必須設爲(注意是:必須):

  • S = 1
  • C/D = 1

說明這個 descriptor 是 code segment descriptor,如果你嘗試加載一個 S = 0 或者 C/D = 0 的 descriptor 進入 CS 寄存器,將會產生 #GP 異常。而下面兩個類型屬性是無效的:

  • R(Readable)
  • A(Accessed)

那麼 Code segment 在 64 位模式下強制爲 Readable 可讀。

b) Data segment descriptor

在 data segment descriptor 情況有些特別。對於加載到 ES, DS, SS 寄存器的 data segment descriptor 來說僅有一個屬性是有效的:

  • P(Present)

對於加載到 FS 和 GS 寄存器的 data segment descriptor 來說 base 是有效的,那麼可以在 FS 和 GS 寄存器的 base 裏設置非 0 的 segment base 值。 同樣必須設置 S 和 C/D 屬性,在 data segment descriptor 裏它們必須爲:

  • S = 1
  • C/D = 0

指示該 descriptor 是 data segment descriptor,如果嘗試加載 S = 0 或者 C/D = 1 的 descriptor 進入 DS,ES,SS,FS 以及 GS 寄存器會產生 #GP 異常。下面的類型屬性是無效的:

  • E(Expand-Down)
  • W(Writable)
  • A(Accessed)

那麼在 64 位模式下,data segment 被強制爲 Expand-Up 和 Writable 的。

2) system descriptors

包括 LDT descriptor、TSS descriptor 。這些 descriptor 被擴展爲 16 個字節共 128 位。descriptor 的 base 域被擴展爲 64 位值。用來在 64 位的線性地址空間中定位。在 compatibility 模式下,LDT / TSS 依舊是 32 位的 descriptor。

system segment descriptor

64 位模式下的 system segment descriptor 是 16 bytes 共 128 位,包括:

  • 20 位的 segment limit
  • 64 位的 segment base
  • 12 位的 segment attribute

在 64 位模式下 user segment descriptor 是 8 bytes,而 system segment descriptor 是 16 bytes 的,它們存放在 GDT 表中就可能會產生了跨 descriptor 邊界的問題。

這裏寫圖片描述

上圖顯示了在 64 位模式下的一個跨 descriptor 邊界產生 #GP 異常的示例:
當使用 selector = 0x20 企圖訪問一個 user segment descriptor,但是並不如願,+0x20 位置上並不是一個有效 user segment descriptor,它只是 LDT descriptor 的高半部分

結果會怎樣?答案是:未知

因此爲了防止這種未知結果的產生,x64 體系中建議:須將 system descirptor(包括 call gate descriptor)的高 64 位中的對應 S 標誌和 type 位置上置00000,但是不包括 interrupt gate 和 trap gate。由於 00000(代表 0 類型的 system descriptor)是無效的 descriptor 類型,因此訪問這樣的 descriptor 會導致 #GP 異常的發生。從而避免未知結果的產生。這就是上圖中上半部分的 S 和 type 域爲何置爲 00000 的原因。當然這個跨 descriptor 邊界的情況在 LDT 也可能發生。但是在 IDT 是不可能發生的,那是因爲 IDT 只能存在 system descriptor 不可存放 user segment descriptor。因此 IDT 表的索引因子固定爲 16,這就是 interrupt gate 和 trap gate 的高半部分 s 和 type 域不用置爲 00000 的原因。

LDT/TSS segment descriptor 大部分屬性是有效的,包括:

  • type
  • S
  • DPL
  • P
  • AVL
  • G

它們的 type 包括:

  • type = 2 :64-bit LDT
  • type = 9 :available 64-bit TSS
  • type = B :busy 64-bit TSS

64 位模式的 system segment descriptor 已經不支持 16 位的 TSS,原來的 32 位 TSS 變成了 64 位的 TSS。
system segment descriptor 在 compatibility mode 下依舊是 32 位的 descriptor,這和 64 bit 模式下區別很大。在一個可以執行 legacy 32 位程序的 OS 裏,應該要準備兩份 LDT/TSS segment descriptor:64 位的 LDT/TSS segment descriptor 和 32 位的 LDT/TSS segment descriptor

3) gate descriptor

long mode 下不存在 task gate。所有的 gate(call、interrupt / trap) 都 64 位的。gate 所索引的 code segment 是 64 位的 code segment(L = 1 && D = 0)

注意:
1、long mode 下的 segment descriptor 與 x86 原有的 segment descriptor 格式完全一致,只是在 64 bit 模式中 descriptor 的大部分域是無效的。
2、64 bit 模式下的 system descriptor 被擴展爲 16 個字節。由於 system descriptor 中的 base 是有效的,base 被擴展爲 64 位,故 system descriptor 被擴展爲 128 位。

2.3.1.3. descriptor table

在 long mode 下 GDT 可以容納:

  • 32 位的 code segment descriptor
  • 32 位的 data segment descripotr
  • 64 位的 LDT segment descriptor
  • 64 位的 TSS segment descriptor
  • 64 位的 call gate descriptor

全部 system descriptor(包括:LDT/TSS descriptor 和 call gate descriptor)都擴展爲 64 位的 descriptor

注意:這些 system GDT entries 是 16 bytes 128 位的大小,這裏所說的 64 位的descriptor 是指 descriptor 的類型是 64 位,它的大小實際上 16 bytes,上文已經提過在 long mode 下“跨 descriptor 邊界”問題的產生就是因爲這裏有 32 位的 descriptor 和 64 位的 descriptor 同時存放在 GDT 裏所造成的。

1) long mode 下 GDT 的 base

GDTR.base 擴展爲 64 位,因此 GDT 可以在 64 位線性空間的任何位置,但 limit 還是 16 位不變。

2) long mode 下 GDT 的索引

long modeGDT 依然是按以前的方式索引查找 descriptor,即:selctor.SI * 8 而不是 selector.SI * 16,這是因爲還存在 32 位的 code/data segment descriptor 的緣故。這是造成跨 descriptor 邊界的原因

3) long mode 下的 LDT

在 long mode 下的 LDT 可以存放:

  • 32 位 code/data segment descriptor
  • 64 位的 call gate desciptor

因此,在 LDT 同樣存在“跨 descriptor 邊界”的問題,IDTR.base 被擴展爲 64 位,它的值由 64 位的 LDT segment descriptor 加載而來

4) long mode 下的 IDT

在 long mode 下 IDT 可以存放:

  • 64 位的 interrupt gate descriptor
  • 64 位的 trap gate descriptor

在 long mode 的 IDT 裏不存在跨 descriptor 邊界問題,interrupt vector 的索引大小固定是 16 bytes

這裏寫圖片描述

2.4. 其他模式

2.4.1. 系統管理模式

系統管理模式,System Management Mode,系統管理模式(SMM)是Intel在386SL之後引入x86體系結構的一種CPU的執行模式。系統管理模式只能通過系統管理中斷(System Management Interrupt, SMI)進入,並只能通過執行RSM指令退出。SMM模式對操作系統透明,換句話說,操作系統根本不知道系統何時進入SMM模式,也無法感知SMM模式曾經執行過。爲了實現SMM,Intel在其CPU上新增了一個引腳SMI# Pin,當這個引腳上爲高電平的時候,CPU會進入該模式。在SMM模式下一切被都屏蔽,包括所有的中斷。SMM模式下的執行的程序被稱作SMM處理程序,所有的SMM處理程序只能在稱作系統管理內存(System Management RAM,SMRAM)的空間內運行。可以通過設置SMBASE的寄存器來設置SMRAM的空間。SMM處理程序只能由系統固件實現。下圖顯示了SMM與其他處理器運行模式(保護模式,實模式和虛擬- 8086)之間的切換過程。

這裏寫圖片描述

2.4.2. 虛擬8086模式

虛擬8086模式,Virtual-8086 Mode,V86模式,在保護模式下CPU可以進入到這種模式,即虛擬8086模式是保護模式下的一種工作方式。CPU把V86任務作爲與其它任務具有同等地位的一個任務,可以支持多個V86任務,每個V86任務是相對獨立的。在虛擬8086模式下,處理器工作方式類似於8086。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章