linux分段內存管理中的GDT,LDT,GDTR,LDTR

本文是對上一篇文章《邏輯地址、線性地址、物理地址的關係以及段寄存器在不同位數CPU中的用途演變以及GDT LDT PGD PT的關係》的補充。

一. 尋址方式:實地址模式和保護地址模式

我們知道,內存尋址模式在早期是採用的實地址模式(intel 80286之前),後面發展到了保護模式(80286開始)。在8086的時候,也就是16位cpu的時候,CPU配備了4個16位段寄存器(CS代碼段寄存器、DS數據段寄存器、SS堆棧段寄存器和ES附加寄存器),當時設計的尋址空間爲1M。而1M是2^{20},因此需要20位寬,而寄存器是16位,怎麼辦呢?

段寄存器裏面存儲的是實際物理內存地址的高16位,CPU要處理的訪存指令裏給出的是低16位的地址(內部地址)。這樣,高16位的地址後面加上0000得到基址,再和低16位地址(偏移量)相加,就得到了物理內存地址,這就是所謂的[段寄存器基址:段內偏移量]方式的尋址。注意,這裏得到的是物理內存地址,可不上上文說的虛擬地址等等。

而在8086年代,用戶可以隨意修改四個段寄存器的內容,同時低16位地址可以是有效範圍內的任意值(64K空間)。這樣,就意味着你可以隨意(讀和寫)訪問物理內存中的任何位置的數據,就問你怕不怕?從上面的表述,你能發現,在8086年代,系統是對物理內存沒有任何保護和訪問控制的吧。這就是所謂的實地址模式(和保護模式相對應的)

到了80286,考慮到實地址模式的潛在危險(這個已經被人利用過了),intel將尋址方式改成了保護模式,但改的不夠徹底,能從實地址模式轉到保護模式,但不能反向轉換。從80386開始纔算是徹底轉換成功,從此開始了32位cpu的時代。考慮到向前兼容性,80386還得支持實地址模式,所以只能基於原來的4個段寄存器進行修修補補。但是這一次不是小打小鬧,而是進行了重大改進。上文說過了,80386增加了段寄存器的數量。設計思想是:在保護模式下,改變段寄存器的功能,不再表示單一的一個基地址,變成指向一個數據結構的指針。這個結構是啥呢?就是段描述符Segment Descripter了(它長度爲64bit,8個字節,每一描述符描述一個段的信息),而段描述符是聚集在一起存儲的,那個結構叫段描述符表,Segment Descripter table。又根據這個段描述符表裏存的是全局共同的,還是進程私有的,分成了全局段描述符表(GDT)和局部段描述符表(LDT)。

那麼80386的段寄存器裏存的是什麼呢?雖然說要存指向段描述符的指針,但是發現用不到16位那麼多,那就不能浪費啊。用1位表示從LDT還是GDT找段描述符,找哪個呢?LDT和GDT都可以看做數據(順序連續存儲的)用13位表示下標吧。剩下2位當成訪問控制吧。這就是所謂的“段選擇符”或者“段選擇子”。

二. 描述符表

全局描述符表GDT

在整個系統中只有一張(一個處理器對應一個GDT),GDT可以被放在內存的任何位置,但CPU必須知道GDT的入口,也就是基地址放在哪裏,Intel的設計者提供了一個寄存器GDTR用來存放GDT的入口地址,程序員將GDT設定在內存中某個位置之後,可以通過LGDT指令將GDT的入口地址裝入此寄存器,從此以後,CPU就根據此寄存器中的內容作爲GDT的入口來訪問GDT了。GDTR中存放的是GDT在內存中的基地址和其表長界限。

基地址指定GDT表中字節0在線性地址空間中的地址,表長度指明GDT表的字節長度值。指令LGDT和SGDT分別用於加載和保存GDTR寄存器的內容。在機器剛加電或處理器復位後,基地址被默認地設置爲0,而長度值被設置成0xFFFF。在保護模式初始化過程中必須給GDTR加載一個新值。

GDTR

段選擇子(Selector)

由GDTR訪問全局描述符表是通過“段選擇子”(實模式下的段寄存器)來完成的。段選擇子是一個16位的寄存器(同實模式下的段寄存器相同)

段選擇子

段選擇子包括三部分:描述符索引(index)、TI、請求特權級(RPL)。它的index(描述符索引)部分表示所需要的段的描述符在描述符表的位置,由這個位置再根據在GDTR中存儲的描述符表基址就可以找到相應的描述符。然後用描述符表中的段基址加上邏輯地址(SEL:OFFSET)的OFFSET就可以轉換成線性地址,段選擇子中的TI值只有一位0或1,0代表選擇子是在GDT選擇,1代表選擇子是在LDT選擇。請求特權級(RPL)則代表選擇子的特權級,共有4個特權級(0級、1級、2級、3級)。

關於特權級的說明:任務中的每一個段都有一個特定的級別。每當一個程序試圖訪問某一個段時,就將該程序所擁有的特權級與要訪問的特權級進行比較,以決定能否訪問該段。系統約定,CPU只能訪問同一特權級或級別較低特權級的段。

例如給出邏輯地址:21h:12345678h轉換爲線性地址

a. 選擇子SEL=21h=0000000000100 0 01 (b) 代表的意思是:選擇子的index=4即0100,選擇GDT中的第4個描述符;TI=0代表選擇子是在GDT選擇;最後的01代表特權級RPL=1

b. OFFSET=12345678h若此時GDT第四個描述符中描述的段基址(Base)爲11111111h,則線性地址=11111111h+12345678h=23456789h

局部描述符表LDT

局部描述符表LDT(Local Descriptor Table)局部描述符表可以有若干張,每個任務可以有一張。我們可以這樣理解GDT和LDT:GDT爲一級描述符表,LDT爲二級描述符表。如圖

LDT和GDT從本質上說是相同的,只是LDT嵌套在GDT之中。LDTR記錄局部描述符表的起始位置,與GDTR不同,LDTR的內容是一個段選擇子。由於LDT本身同樣是一段內存,也是一個段,所以它也有個描述符描述它,這個描述符就存儲在GDT中,對應這個表述符也會有一個選擇子,LDTR裝載的就是這樣一個選擇子。LDTR可以在程序中隨時改變,通過使用lldt指令。如上圖,如果裝載的是Selector 2則LDTR指向的是表LDT2。舉個例子:如果我們想在表LDT2中選擇第三個描述符所描述的段的地址12345678h。

1. 首先需要裝載LDTR使它指向LDT2, 使用指令lldt將Select2裝載到LDTR

2. 通過邏輯地址(SEL:OFFSET)訪問時SEL的index=3代表選擇第三個描述符;TI=1代表選擇子是在LDT選擇,此時LDTR指向的是LDT2,所以是在LDT2中選擇,此時的SEL值爲1Ch(二進制爲11 1 00b)。OFFSET=12345678h。邏輯地址爲1C:12345678h

3. 由SEL選擇出描述符,由描述符中的基址(Base)加上OFFSET可得到線性地址,例如基址是11111111h,則線性地址=11111111h+12345678h=23456789h

4. 此時若再想訪問LDT1中的第三個描述符,只要使用lldt指令將選擇子Selector 1裝入再執行2、3兩步就可以了(因爲此時LDTR又指向了LDT1)

由於每個進程都有自己的一套程序段、數據段、堆棧段,有了局部描述符表則可以將每個進程的程序段、數據段、堆棧段封裝在一起,只要改變LDTR就可以實現對不同進程的段進行訪問

當進行任務切換時,處理器會把新任務LDT的段選擇符和段描述符自動地加載進LDTR中。在機器加電或處理器復位後,段選擇符和基地址被默認地設置爲0,而段長度被設置成0xFFFF。

1:訪問GDT

段描述符在GDT中

當TI=0時表示段描述符在GDT中,如上圖所示:

①先從GDTR寄存器中獲得GDT基址。

②然後再GDT中以段選擇器高13位位置索引值得到段描述符。

③段描述符符包含段的基址、限長、優先級等各種屬性,這就得到了段的起始地址(基址),再以基址加上偏移地址yyyyyyyy纔得到最後的線性地址。

2:訪問LDT

當TI=1時表示段描述符在LDT中,如上圖所示:

①還是先從GDTR寄存器中獲得GDT基址。

②從LDTR寄存器中獲取LDT所在段的位置索引(LDTR高13位)。

③以這個位置索引在GDT中得到LDT段描述符從而得到LDT段基址。

④用段選擇器高13位位置索引值從LDT段中得到段描述符。

⑤段描述符符包含段的基址、限長、優先級等各種屬性,這就得到了段的起始地址(基址),再以基址加上偏移地址yyyyyyyy纔得到最後的線性地址。

擴展

除了GDTR、LDTR外還有IDTR和TR

(1)中斷描述符表寄存器IDTR 

與GDTR的作用類似,IDTR寄存器用於存放中斷描述符表IDT的32位線性基地址和16位表長度值。指令LIDT和SIDT分別用於加載和保存IDTR寄存器的內容。在機器剛加電或處理器復位後,基地址被默認地設置爲0,而長度值被設置成0xFFFF。

(2)任務寄存器TR

TR用於尋址一個特殊的任務狀態段(Task State Segment,TSS)。TSS中包含着當前執行任務的重要信息。

TR寄存器用於存放當前任務TSS段的16位段選擇符、32位基地址、16位段長度和描述符屬性值。它引用GDT表中的一個TSS類型的描述符。指令LTR和STR分別用於加載和保存TR寄存器的段選擇符部分。當使用LTR指令把選擇符加載進任務寄存器時,TSS描述符中的段基地址、段限長度以及描述符屬性會被自動加載到任務寄存器中。當執行任務切換時,處理器會把新任務的TSS的段選擇符和段描述符自動加載進任務寄存器TR中。

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