歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> Linux0.11內核--32位保護模式GDT(全局描述符表)

Linux0.11內核--32位保護模式GDT(全局描述符表)

日期:2017/3/1 10:24:03   编辑:Linux編程

在Protected Mode下,一個重要的必不可少的數據結構就是GDT(Global Descriptor Table)。

為什麼要有GDT?我們首先考慮一下在Real Mode下的編程模型:

在Real Mode下,我們對一個內存地址的訪問是通過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。

到了Protected Mode,內存的管理模式分為兩種,段模式和頁模式,其中頁模式也是基於段模式的。也就是說,Protected Mode的內存管理模式事實上是:純段模式和段頁式。進一步說,段模式是必不可少的,而頁模式則是可選的——如果使用頁模式,則是段頁式;否則這是純段模式。

既然是這樣,我們就先不去考慮頁模式。對於段模式來講,訪問一個內存地址仍然使用Segment:Offset的方式,這是很自然的。由於 Protected Mode運行在32-bit系統上,那麼Segment的兩個因素:Base Address和Limit也都是32位的。IA-32允許將一個段的Base Address設為32-bit所能表示的任何值(Limit則可以被設為32-bit所能表示的,以2^12為倍數的任何指),而不象Real Mode下,一個段的Base Address只能是16的倍數(因為其低4-bit是通過左移運算得來的,只能為0,從而達到使用16-bit段寄存器表示20-bit Base Address的目的),而一個段的Limit只能為固定值64 KB。另外,Protected Mode,顧名思義,又為段模式提供了保護機制,也就說一個段的描述符需要規定對自身的訪問權限(Access)。所以,在Protected Mode下,對一個段的描述則包括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是Protected Mode所必須的數據結構,也是唯一的——不應該,也不可能有多個。另外,正象它的名字(Global Descriptor Table)所揭示的,它是全局可見的,對任何一個任務而言都是這樣。

除了GDT之外,IA-32還允許程序員構建與GDT類似的數據結構,它們被稱作LDT(Local Descriptor Table),但與GDT不同的是,LDT在系統中可以存在多個,並且從LDT的名字可以得知,LDT不是全局可見的,它們只對引用它們的任務可見,每個任務最多可以擁有一個LDT。另外,每一個LDT自身作為一個段存在,它們的段描述符被放在GDT中。

IA-32為LDT的入口地址也提供了一個寄存器LDTR,因為在任何時刻只能有一個任務在運行,所以LDT寄存器全局也只需要有一個。如果一個任務擁有自身的LDT,那麼當它需要引用自身的LDT時,它需要通過LLDT將其LDT的段描述符裝入此寄存器。LLDT指令與LGDT指令不同的時,LGDT指令的操作數是一個32-bit的內存地址,這個內存地址處存放的是一個32-bit GDT的入口地址,以及16-bit的GDT Limit。而LLDT指令的操作數是一個16-bit的選擇子,這個選擇子主要內容是:被裝入的LDT的段描述符在GDT中的索引值——這一點和剛才所討論的通過段積存器引用段的模式是一樣的。

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,就得到了最後的內存地址。

以上摘自互動百科

GDT的結構圖如下:

G:G=0時,段限長的20位為實際段限長,最大限長為2^20=1MB

G=1時,則實際段限長為20位段限長乘以2^12=4KB,最大限長達到4GB


D/B:當描述符指向的是可執行代碼段時,這一位叫做D位,D=1使用32位地址和32/8位操作數,D=0使用16位地址和16/8位操作數。如果指向的是向下擴展的數據段,這一位叫做B位,B=1時段的上界為4GB,B=0時段的上界為64KB。如果指向的是堆棧段,這一位叫做B位,B=1使用32位操作數,堆棧指針用ESP,B=0時使用16位操作數,堆棧指針用SP。

DPL:特權級,0為最高特權級,3為最低,表示訪問該段時CPU所需處於的最低特權級

type : 類型

type<8時

type>=8時

Copyright © Linux教程網 All Rights Reserved