2009年3月24日 星期二
●描述符表和描述符快取記憶體
在80x86的CPU裡,描述符的概念實在是太重要了。
在真實模式下,大家都知道物理位址是由段位址和偏移位址兩部分組成,其公式如下:
物理位址 = 段位址 × 16 + 偏移位址
或者:物理位址 = 段位址 << 4 + 偏移位址
其結果都是一樣的,由於段位址和偏移位址的長度都是16位元,所以這種方式能夠表達的最大位址為:ffff:ffffH,也就是10ffefH,大致是 1088KBytes,有由於8086CPU的位址線只有20位,所以在8086上實際的定址能力僅為1024KBytes,在80286和 80386CPU上,通過A20的使用,可以實際定址到1088KBytes,這個問題在《關於A20 gate》的文章中有過介紹。
從80386開始,CPU的位址匯流排已經到了32位元,但只有在保護模式下才能真正地享受32位的高性能,實際上在保護模式下,CPU也是使用段:偏移位址 的方式來定址的,只是這裡面有兩點區別,1-偏移位址可以是16位也可以是32位;2-段寄存器中的內容和在真實模式下的含義完全不一樣,而且已經不是組成 物理位址的一部分。顯然,偏移位址的這種變化是很容易理解的,無需更多地解釋,所以,保護模式和真實模式相比,重要的區別就是段寄存器中內容的區別了。
那麼,這個段寄存器中到底存的是什麼東東呢?它存的是某個描述符在描述附表中的偏移值,這是什麼意思呢?一個描述附表中有很多描述符,每個描述符的長度都 相同,假如第一個描述符我們編號是0的話,那麼第n個描述符的編號就是n-1,這個n-1就是所謂的描述符索引,所謂“偏移”就是從描述符表起始位置起, 到我們要用的這個描述符有多少個位元組,也就是描述符索引 × 描述符長度,很顯然如果知道描述符表的起始位址,再加上段寄存器的值,我們就能找到我們要的這個描述符了。
我們先來把到此為止的問題羅列一下:
1、這個描述符是什麼東東?
2、畢竟我們是要通過段:偏移位址的形式得到物理位址,既然這個段代表一個描述符,那麼這個描述符和物理位址有什麼關係?
3、上面說到的描述符表的起始位址和描述符長度是是什麼?因為沒有這兩個東西還是找不到描述符的。
我們試著來說明這幾個問題。
首先,描述符實際上就是一個8位元組長的資料結構,它的定義如上:
每一個描述符代表一個分段(有點像真實模式下的64K分段),可以看到段基址由Byte2、Byte3、Byte4和Byte7組成,一共32bits,段 的最大長度不像在真實模式中固定為64K,而是有一個20位的段邊界(由Byte0、Byte1和Byte6的低4位組成)來設定,由於偏移位址為32位, 所以實際上段的最大長度可以達到4GBytes,段邊界的單位可以是位元組也可以是4KBytes,這取決於G=0還是G=1(Byte6的bit 7),當G=1時,段邊界的單位是4KBytes,相當於低12bits全部為1(4K剛好12bits),加上20位的段邊界,剛好為32位,最大可以 達到4GBytes;當G=0時,段邊界的單位為位元組,20位元最大為16MBytes。
在描述符的定義中還有一個訪問權位元組以及AVL、D/B等,介紹起來篇幅很長,以後有機會介紹。
說到這兒,可能大家心裡有點眉目了,段寄存器裡存放一個描述符在描述符表中的偏移,通過這個偏移可以找到相應的描述符,通過這個描述符中的段基址再加上偏移位址,就組成了物理位址,對了,基本上就是這樣,當然不會這麼簡單,其中還有很多細節,但大致原理就是這樣。
前面提到的三個問題,我們已經回答了兩個半,就是描述符是什麼,怎樣計算物理位址,還有描述符的長度是多少。最後一個問題是:描述符表的起始位址在那裡? 其實這個問題最好回答,在那篇《80386寄存器組成》的文章中提到過一個GDTR的系統表寄存器,這個描述符表的起始位址就存在這個寄存器中(這麼說不 是很完整,但不影響理解),如果你還要問,描述符表存在哪裡?如何把描述符表的起始位址放到GDTR寄存器中?這兩個問題也不難回答,描述符表可以存儲在 記憶體的任何位置;通過LGDT指令可以把描述符表的起始位址存入GDTR寄存器中。
大家有沒有感到奇怪,為什麼32位的段基址和20位的段界限在描述符中都不連續存放,這是和Intel 80x86 CPU的發展歷史分不開的,最先有保護模式的CPU是80286(現在已經不多見了),這個CPU只有24位的位址線,所以可以看到現在386的描述符中 基底位址的前24bits是連續存放的,後來在擴充時需要把基底位址變成32位元,為了和80286相容,只好分開存放了,段界限也是同樣原因造成的,所以在 80286上,一個描述符的長度雖然也是8位元組長,但它只用到了前6個位元組,到了386就全都用上了。
到這裡,應該對保護模式下記憶體的定址有了一個大致的瞭解,CPU首先通過段寄存器中的偏移值,配合GDTR寄存器中的描述符表的起始位址,找到段寄存器表示的描述符,再從描述符中獲得段基址,然後再加上偏移位址就得到了物理位址(其中許多細節省略不說)。
大家有沒有感到,CPU的定址過程很累,那麼這麼累的定址會不會導致速度變慢呢?實際上,CPU的定址過程並不像上面描述的這麼累,要解釋CPU的實際定址過程,必須要提到描述符快取記憶體(Descriptor Cache Register)。
實際上,不管是在真實模式還是在保護模式下,CPU都會把一個分段的基底位址放在一組隱藏的寄存器中,這組隱藏的寄存器,對程式師是不可見的,程式也是無法直 接存取的,但卻是實際存在的,這組隱藏的寄存器叫做描述符快取記憶體寄存器(Descriptor Cache Registers),當段寄存器的值發生變化時,段的基底位址、段的邊界以及存取屬性(存取許可權)都會被重新載入到這個段寄存器對應的快取記憶體中,為增強 性能,CPU對隨後的定址均會直接從這個快取記憶體中計算,而不會去描述符表中提取描述符,實際上,各種CPU中這個快取記憶體的內部結構是不同的,以上是幾種CPU中快取記憶體的結構圖:
不管其內部結構是怎樣的,但我們可以看到,CPU在把描述符載入進快取記憶體時並不是簡單的拷貝,而是做了一些適當的處理,比如把原來沒有連續存放的段基址 和段界限變成了連續的,把原來20bits的段界限根據G值轉換成了32bits,所以我們在快取記憶體裡看不到原來描述符裡的G標誌就是這個道理,顯然, 這些處理十分有利於快速地得出實際位址來。
前面我們提到過,CPU內部寄存器GDTR中存儲著描述符表的起始位址,細心的讀者可能會發現,《80386寄存器組成》一文中提到的GDTR寄存器有 48bits,而且分成兩部分,一部分是32bits,另一部分是16bits,這是怎麼回事呢?實際上GDTR中不僅存著描述符表的其真實位址,還存放著 描述符表的長度,其中16bits的部分就是描述符表的長度,32bits的部分就是描述符表的起始位址,按照規範,描述符表中最多可以有 8192(8K)個描述符,每個長度8個位元組,所以最大長度為64K,16bits已經足夠了,同理,段寄存仍然保持16bits長度也是足夠的。
不管是在真實模式還是在保護模式下,CPU在實際定址時都會使用這個快取記憶體。所不同的是,在真實模式下,在段寄存器的值發生變化時,僅僅把段寄存器的值 ×16(左移4位)放到快取記憶體的的基底位址位置,段界限和存取許可權總是一個固定不變的值(按照Intel的說法,PC機加電後工作在真實模式,快取記憶體中將 被置入缺省值,在真實模式下,其中的段界限和存取許可權將一直保持不變);而在保護模式下,當段寄存器發生變化時,CPU要從描述符表中載入資料到快取記憶體 中,實際上,保護模式下CPU在從描述符表中向快取記憶體中載入資料時要做大量的保護性檢查,大致如下:
1. 段寄存器的值不能是0。根據規範,描述符表中的第一個描述符必須是空描述符,所以段寄存器值為0是不合法的。如果為0,產生異常中斷13.
2. 段寄存器中的值是否大於或等於描述符表的長度(存在GDTR中),如果大於或等於描述符表的長度,產生異常中斷13.
3. 如果段寄存器是CS,檢查描述符表中的段類型是否為程式碼片段,如果不是,產生異常中斷13.
4. 如果段寄存器CS要求裝入的段是程式碼片段,檢查描述符表該段是否存在,如果不存在產生異常中斷11
5. 如果段寄存器CS通過了3、4的檢查,還要檢查IP是否超越了該段的邊界,如果越界,產生異常中斷13
6. 如果段寄存器CS通過了3、4、5的檢查,則把相應的描述符裝入快取記憶體
7. 如果段寄存器不是CS,檢查描述符表中的段類型為資料段,如果不是產生異常中斷13
8. 如果段寄存器不是CS,該段為資料段,檢查其是否存在,如果不存在產生異常中斷12
9. 如果段寄存器不是CS,且通過了7、8檢查,則把相應的描述符裝入快取記憶體
以上過程,不一定很完整,大概就是這樣,從中,大家應該可以看出,所謂保護模式的保護方式,至少有一個感性認識。
說到這裡,基本上可以結束了,但是我們所說的描述符表實際上是非常膚淺的,實際上本文說到的描述符表叫做通用描述元表(Global Descriptor Table 簡稱GDT),還有局部描述符表,中斷描述符表等,但其原理大同小異,理解了GDT,其它的也就比較容易了
訂閱:
張貼留言 (Atom)
沒有留言:
張貼留言