80286與保護模式

x86 CPU有實模式、保持模式、虛擬8086模式、系統管理模式等的分別。 x86 CPU只有在啟動的時候才能進入實模式,一旦切換到保持模式就無法退出回到實模式。

實模式與保護模式

簡單的講,實模式就是8086使用CPU的模式。當然那時還沒有實模式的叫法。只有後面有保護模式,舊的模式才有了實模式的稱呼。

簡要介紹實模式如下:

* 16位寄存器

* 20位地址線,可訪問1MB內存

* 通過CS/DS寄存器左移4位+IP寄存器的值生成20位訪問地址

這裡有兩個問題值得注意: 1. CS<<4 + IP理論上來講,最大可以表示的數值是0xFFFF0 + 0xFFFF = 0x10FFEF,即大約1M+64KB-16Bytes,然而由於地址線只有20根(A0~A19),這個地址最前面的1無法被表示,當CS=0xFFFF時,實際訪問的地址0x10FFEF就變成了0xFFEF。即地址由卷回了0地址到64KB-16Bytes處。 2. 由於程序可以任意修改當前的CS/DS值,所有程序可以使用全部1MB的內存,所以這個CPU幾乎沒有辦法有效地支持多任務,因為兩個程序一起運行的話很容易互相踩到內存。所以當時的使用的方式系統中同時運行的只有一個應用程序和一個DOS操作系統。操作系統和應用規定了各自能使用的內存地址範圍,比如說DOS只使用高64KB的內存,其它的內存給應用程序使用。這樣就可以互不影響。要想運行另一個應用程序必須先退出當前運行的應用程序。

上面的第二個問題其實就是保護模式「保護」二字的來源。保護模式的初衷就是保護一個進程的內存不被其他進程非法訪問。 上面的第一個問題之所以被提到是因為它在80286設計的時候需要做特殊的處理。

80286的保護模式

實模式即實際地址模式,用戶寫的代碼中CS與IP寄存器組合成的地址即為實際的物理內存地址。這種方式非常的不安全。不方便實現多任務系統。在設計80286的時候要解決的主要就是這個問題。 在80286中,段式訪存得到的改進,原來段寄存器+IP得到的地址不在是實際的物理地址,而是要經過一個轉換層轉換才變成一個物理地址。CS裡面的內存不再是20位物理地址的高16位,而變成了段描述符表中的索引。

如圖所示: 原來16位的段寄存器改名叫段選擇子。現在是如何工作的,如何控制一個程序可以訪問的內存範圍呢?

答案: DOS系統在啟動用戶應用程序時先在內存中設置兩個表,一個叫全局描述符表,GDT,一個叫局部描述符表LDT。表中的每個條目實際代表一段內存地址,包含這段內存的啟始地址和段的長度,即base和limit。一個表的多個條目即代碼這個用戶程序可以訪問的多塊內存。

DOS系統把這兩個表的內存啟始地址寫到LDTR和GDTR兩個寄存器里。並在CS、DS寄存器中設置好一個初始的索引。

應用程序在訪問內存時,CS:IP組合稱為邏輯地址,CPU拿CS中的3~15位所組成的數據作為索引去LDT或GDT中找到一個描述符,拿這個描述符的base + IP做為實際的物理內存地址去訪存。LDT和GDT只有DOS操作系統才能修改,因而應用程序只能訪問DOS為它設置好的內存範圍,而不能隨意訪問全部物理內存。

應用程序內存不夠用時,需要調用一些系統調用,讓DOS分配一段內存,把將這段內存的base, limit做成一個描述符加入到GDT或LDT中。

應用程序如果胡亂修改CS,造成無法索引到一個有效的段描述符就會發一個「段錯誤, segmentation fault」。

如圖所示,在80286中,CS/DS/ES/FS寄存器的意義發生了變化。它的3~15位是描述符表的索引。TI用來表示索引的是哪一張表,是GDT還是LDT。RPL稱為請求許可權級別。RPL對應描述符中的DPL,只要RPL的級別高於(值小於)DPL時,才有許可權訪問這段內存。 描述符中的DPL如下圖所示:

80286的多任務

藉助段描述符表,系統可以為應用程序分配內存,並且限制用戶對內存的訪問。這時候,多任務的方式匯總如下圖:

基於這種內存管理方式,用戶應用程序可以實現動態鏈接。比如說一個程序分為代碼段、數據段、零初始化段等,它依賴的庫也是分段的,系統在載入程序時,只需為每個段分配一段內存,並為每個段設置一個描述符即可。 每個段的起始地址可以在載入時根據實際情況修改。

代碼段設置成只讀只需要其描述符的TYPE欄位即可。

A20 Gate和16MB內存

80286的寄存器仍然是16位寬,但它的內存地址匯流排是24位寬。可以訪問16MB內存。在保護模式下,由於訪存方式發生了變化,要訪問16MB內存,只需在描述符中的base位寬可以達到24位即可。

然而在實地址模式下,訪存方式為CS<<4 +IP,最多只能表示0x10FFEF地址,即1MB+64KB-16Byte。這種訪問方式已經沒有任何優勢,但是為了兼容舊的程序,80286以到最新的x86體系CPU在啟動的時候都支持實模式,只能訪問1MB+64KB-16Bytes的內存。

對於8086而言,由於地址線只有A0~A19,訪問不了大於1MB的內存。然而80286由於有24根地址線,0x10FFEF這個數字中最高位的1可以體現在A20地址線上,從而訪問1MB~ 1MB+64KB-16Bytes之間的內存。 這個1要不要體現在A20上,體現在A20上是一種很自然的想法,intel也是這樣做的。然而這成了一個bug。

在8086上,超過1MB的地址訪問會卷回0地址。這個是偶然的現像,本來就不是一個feature,而更像一個考慮不周的bug。然而實際程序開發中,很多為8086開發的程序利用了這一特性去訪問0開頭的低64KB內存。在80286上,利用了這個bug的程序就不工作了。於是這成了80286的一個bug。真讓人無語。

為了讓80286在實模式的行為和8086一樣,IBM想出一個work around,即在A20總上加一個與邏輯門,與邏輯門的另一個輸入信號接在鍵盤控制器晶元8042上,在8042中增加一組鍵盤控制組合鍵,可以控制A20線上信號的最終輸出:

這個與邏輯門稱為A20 Gate。給 8042 發送命令 0xDF 置 A20 gate 有效,給 8042 送命令 0xDD 置 A20 gate 無效

在現在CPU中,8042已經被集成在南橋上,A20 gate也被做成了標準功能,稱為Fast A20 Gate,對A20的控制變成了對0x92埠上的bit 1的讀寫。打開A20 Gate的代碼如下:

openA20: in al, 92h or al, 00000010b out 92h, al

80286描述符表

在80286中有三種類型的描述符表:全局描述符表GDT(Global Descriptor Table)、局部描述符表LDT(Local Descriptor Table)和中斷描述符表IDT(Interrupt Descriptor Table)。

在整個系統中,全局描述符表GDT和中斷描述符表IDT只有一張,局部描述符表可以有若干張,每個任務可以有一張。任務切換時,局部描述符表也跟著切換,具體表現就是將LDTR中的值修改為當前任務對應的LDT的基地址。在任務切換時,切換LDT,並不切換GDT。通過LDT可以使各個任務私有的各個段與其它任務相隔離,從而達到受保護的目的。通過GDT可以使各任務都需要使用的段能夠被共享。通過GDT在多個任務間共享內存非常方便。

每個描述符表本身形成一個特殊的數據段。這樣的特殊數據段最多可包含有8K(8192)個描述符.

描述符表寄存器

描述符表可以放在內存中的任意位置,但CPU必須知道他們在哪裡。因而每個描述符表都對應一個寄存器。即: GDTR LDTR IDTR

細說LDT

局部描述符表寄存器LDTR規定當前任務使用的局部描述符表LDT。 LDTR類似於段寄存器,由程序員可見的16位的寄存器和程序員不可見的高速緩衝寄存器組成。實際上,每個任務的局部描述符表LDT作為系統的一個特殊段,由一個描述符描述。而用於描述符LDT的描述符存放在GDT中。

在初始化或任務切換過程中,把描述符對應任務LDT的描述符的選擇子裝入LDTR,處理器根據裝入LDTR可見部分的選擇子,從GDT中取出對應的描述符,並把LDT的基地址、界限和屬性等信息保存到LDTR的不可見的高速緩衝寄存器中。

隨後對LDT的訪問,就可根據保存在高速緩衝寄存器中的有關信息進行合法性檢查。

LDTR寄存器包含當前任務的LDT的選擇子。所以,裝入到LDTR的選擇子必須確定一個位於GDT中的類型為LDT的系統段描述符,也即選擇子中的TI位必須是0,而且描述符中的類型欄位所表示的類型必須為LDT。

可以用一個空選擇子裝入LDTR,這表示當前任務沒有LDT。在這種情況下,所有裝入到段寄存器的選擇子都必須指示GDT中的描述符,也即當前任務涉及的段均由GDT中的描述符來描述。

如果再把一個TI位為1的選擇子裝入到段寄存器,將引起異常。

細說IDT

中斷描述符表本質上是不是段描述符表,而是一個中斷向量表。 中斷描述符表寄存器IDTR指向中斷描述符表IDT。 IDTR長48 位,其中32位的基地址規定IDT的基地址,16位的界限規定IDT的段界限。

由於80386只支持256個中斷/異常,所以IDT表最大長度是2K,以位元組位單位的段界限為7FFH。IDTR 指示IDT的方式與GDTR指示GDT的方式相同。

任務狀態段

系統中的每一個進程(任務)都有一個描述進程的信息需要保存,在x86中,專門將這些數據保存在一個段里。每個任務都有一個段,這些段的描述符都保存在GDT中,且類型為TSS。代碼段描述符的選擇子是CS/ES之類的寄存器,數據段的描述符的選擇子是DS寄存器。任務狀態段的描述符選擇子也有一個單獨的寄存器叫TR。一個進程可以隨時通過TR得到該進程存在任務狀態段中的信息。

段選擇子

CS/DS/ES/SS/GS/FS在8086中是16位寄存器,用來表示一個段的高16位地址。 在80286中,它們代表在段描述符表中的索引。在訪問內存時,先從段描述符表中查到對應描述符,取到描述符的base, limit,再與IP相加得到物理內存地址。段描述符表本身是在內存里的,每次訪問內存都要去內存里找段描述符,要當於把一個訪存操作變成了兩個訪問操作,效率較低。因而實際設計晶元時為CS/DS...等關聯了些隱形的不可訪問的寄存器,用來暫存該段選擇子對應的描述符。這樣在不進行段切換的時候,就不必每次訪存都去讀取一個段描述符。

CR0 模式切換

控制寄存器CR0中的位0用PE標記,控制分段管理機制的操作,所以把它們稱為保護控制位。 PE控制分段管理機制。 PE=0,處理器運行於實模式; PE=1,處理器運行於保護方式。

在80386及之後的處理器里增加了分頁管理機器,是否啟用分頁由CR0的位31標記。

切換到保護模式的代碼如下:

; switch to protection modeswitch_proMode: mov eax, cr0 or eax, 1 ; set CR0"s PE bit mov cr0, eax

相關問答

解釋下計算機CPU的保護模式? - 回答作者: Sinaean Dean zhihu.com/question/2527 (想看更多?下載 @知乎 App:weibo.com/p/10040471159 )


推薦閱讀:

SIMD指令集
奔騰四採用的超線程技術與SNB之後的CPU採用的超線程技術是同一個技術嗎?
ARM彙編和X86彙編哪個難?
如何看待李楠《ARM vs X86 之爭塵埃落定》一文?
如何掌握Intel Intrinsic Instruction?

TAG:硬件工程师 | x86-64 | x86 |