1. 程式人生 > >作業系統之GDT和IDT(三)

作業系統之GDT和IDT(三)

一、CPU的工作模式(定址方式)

說GDT需要從CPU的工作模式開始說,在IA32架構(或稱i386、X86-32或X86架構)下,CPU有兩種工作模式:真實模式和保護模式。

CPU復位(reset)或加電(power on)的時候以真實模式啟動,處理器以真實模式工作。在真實模式下,記憶體定址方式和8086相同,由16位段暫存器的內容乘以16(10H)當做段基地址,加上16位偏移地址形成20位的實體地址,最大定址空間1MB,最大分段64KB。可以使用32位指令。32位的x86 CPU用做高速的8086。在真實模式下,所有的段都是可以讀、寫和可執行的。
286架構匯入保護模式,允許硬體等級的儲存器保護。然而要使用這些新的特色,需要額外先前不需要的軟體指令。由於x86微處理機主要的設計規格,是能夠完全地向前兼容於針對先前所有x86晶片所撰寫的軟體,因此286晶片的開機是處於’真實模式’—也就是關閉新的儲存器保護特性的模式,所以可以運行鍼對舊的微處理器所設計的軟體。到現在為止,即使最新的x86 CPU一開始在電源開啟處於真實模式下,也能夠運行鍼對先前任何晶片所撰寫的軟體.[1]

在8086/8088時期,都是16位的CPU,沒有工作模式之分,有16位的暫存器,16位的資料匯流排,20位的地址匯流排,使用“Segment:Offset“定址方式,具有1MB的定址能力。
在80286以後出現真實模式和保護模式的區別,真實模式主要是為了相容之前的CPU架構,可以執行之前的軟體,定址方式和定址能力和8086CPU相同。依照CPU的設計規則,之後的X86CPU都是在真實模式下啟動。
而到了Intel 80386,CPU真正具有32位的暫存器,32位的地址匯流排,一個暫存器就具有具有4GB的定址能力。但為了相容80x86之前的機器,8086以後的機器啟動後仍然進入真實模式下,由16位段暫存器的內容乘以16(10H)當做段基地址,加上16位偏移地址形成20位的實體地址,最大定址空間1MB,最大分段64KB。之後需要手動的切換到保護模式下。
保護模式與真實模式相比,主要是兩個差別:一是提供了段間的保護機制,防止程式間胡亂訪問地址帶來的問題,二是訪問的記憶體空間變大,80386具有32位暫存器,定址可達到4GB。

二、GDT

GDT,即全域性描述表(GDT Global Descriptor Table)。
在保護模式下仍然使用Segment:Offset“的定址方式,但其中的含義有所不同。
首先考慮一下在實時模式下的程式設計模型:
在實時模式下,我們對一個記憶體地址的訪問是通過Segment:Offset的方式來進行的,其中Segment是一個段的Base Address,一個Segment的最大長度是64 KB,這是16-bit系統所能表示的最大長度。而Offset則是相對於此Segment Base Address的偏移量。Base Address+Offset就是一個記憶體絕對地址

。由此,我們可以看出,一個段具備兩個因素:Base Address和Limit(段的最大長度),而對一個記憶體地址的訪問,則是需要指出:使用哪個段?以及相對於這個段Base Address的Offset,這個Offset應該小於此段的Limit。當然對於16-bit系統,Limit不要指定,預設為最大長度64KB,而 16-bit的Offset也永遠不可能大於此Limit。我們在實際程式設計的時候,使用16-bit段暫存器CS(Code Segment),DS(Data Segment),SS(Stack Segment)來指定Segment,CPU將段暫存器中的數值向左偏移4-bit,放到20-bit的地址線上就成為20-bit的Base Address。
到了保護模式,記憶體的管理模式分為兩種,段模式和頁模式,其中頁模式也是基於段模式的。也就是說,保護模式的記憶體管理模式事實上是:純段模式和段頁式。進一步說,段模式是必不可少的,而頁模式則是可選的——如果使用頁模式,則是段頁式;否則這是純段模式。
既然是這樣,我們就先不去考慮頁模式。對於段模式來講,訪問一個記憶體地址仍然使用Segment:Offset的方式,這是很自然的。由於保護模式執行在32位系統上,那麼Segment的兩個因素:Base Address和Limit也都是32位的。IA-32允許將一個段的Base Address設為32-bit所能表示的任何值(Limit則可以被設為32-bit所能表示的,以2^12為倍數的任何值),而不象實時模式下,一個段的Base Address只能是16的倍數(因為其低4-bit是通過左移運算得來的,只能為0,從而達到使用16-bit段暫存器表示20-bit Base Address的目的),而一個段的Limit只能為固定值64 KB。另外,保護模式,顧名思義,又為段模式提供了保護機制,也就說一個段的描述符需要規定對自身的訪問許可權(Access)。所以,在保護模式下,對一個段的描述則包括3方面因素:[Base Address, Limit, Access],它們加在一起被放在一個64-bit長的資料結構中,被稱為段描述符。這種情況下,如果我們直接通過一個64-bit段描述符來引用一個段的時候,就必須使用一個64-bit長的段暫存器裝入這個段描述符。但Intel為了保持向後相容,將段暫存器仍然規定為16-bit(儘管每個段暫存器事實上有一個64-bit長的不可見部分,但對於程式設計師來說,段暫存器就是16-bit的),那麼很明顯,我們無法通過16-bit長度的段暫存器來直接引用64-bit的段描述符。怎麼辦?
解決的方法就是把這些長度為64-bit的段描述符放入一個數組中,而將段暫存器中的值作為下標索引來間接引用(事實上,是將段暫存器中的高13-bit的內容作為索引)。這個全域性的陣列就是GDT。事實上,在GDT中存放的不僅僅是段描述符,還有其它描述符,它們都是64-bit長,我們隨後再討論。
GDT可以被放在記憶體的任何位置,那麼當程式設計師通過段暫存器來引用一個段描述符時,CPU必須知道GDT的入口,也就是基地址放在哪裡,所以Intel的設計者門提供了一個暫存器GDTR用來存放GDT的入口地址,程式設計師將GDT設定在記憶體中某個位置之後,可以通過LGDT指令將GDT的入口地址裝入此暫存器,從此以後,CPU就根據此暫存器中的內容作為GDT的入口來訪問GDT了。
GDT是保護模式所必須的資料結構,也是唯一的——不應該,也不可能有多個。另外,正象它的名字(Global Descriptor Table)所揭示的,它是全域性可見的,對任何一個任務而言都是這樣。

總結一下:
1、在保護模式下,對一個段的描述則包括3方面因素:[Base Address, Limit, Access],它們加在一起被放在一個64-bit長的資料結構中,被稱為段描述符:
段描述符的結構:
這裡寫圖片描述
2、段描述符使用陣列儲存,使用LGDT指令將GDT的入口地址裝入GDTR暫存器。
3、段選擇子,一個16位的資料結構
段選擇子的結構:
這裡寫圖片描述,索引號即作為GDT陣列的下標,索引號只有13位,所以,GDT陣列最多有8192個元素。

三、LDT

LDT只是一個可選的資料結構,你完全可以不用它。使用它或許可以帶來一些方便性,但同時也帶來複雜性,如果你想讓你的OS核心保持簡潔性,以及可移植性,則最好不要使用它。
引用GDT和LDT中的段描述符所描述的段,是通過一個16-bit的資料結構來實現的,這個資料結構叫做Segment Selector——段選擇子。它的高13位作為被引用的段描述符在GDT/LDT中的下標索引,bit 2用來指定被引用段描述符被放在GDT中還是到LDT中,bit 0和bit 1是RPL——請求特權等級,被用來做保護目的,我們這裡不詳細討論它。
前面所討論的裝入段暫存器中作為GDT/LDT索引的就是Segment Selector,當需要引用一個記憶體地址時,使用的仍然是Segment:Offset模式,具體操作是:在相應的段暫存器裝入Segment Selector,按照這個Segment Selector可以到GDT或LDT中找到相應的Segment Descriptor,這個Segment Descriptor中記錄了此段的Base Address,然後加上Offset,就得到了最後的記憶體地址。

三、IDT

IDT,Interrupt Descriptor Table,即中斷描述符表,和GDT類似,他記錄了0~255的中斷號和呼叫函式之間的關係。
1、中斷描述符結構:
這裡寫圖片描述
2、段描述符使用陣列儲存,使用LIDT指令將IDT的入口地址裝入IDTR暫存器。