80386學習(二) 80386特權級保護


一、80386特權級保護介紹

  80386CPU為了給操作系統提供硬件級的可靠保護,提供了特權級保護功能。80386處於保護模式時,會改變CPU的行為方式,其中便包括開啟特權級保護。實現良好的特權級保護是需要軟硬件相協調的,CPU提供硬件機制的同時也需要與操作系統相配合,共同實現完善的特權級保護功能。

  要較為全面的理解特權級保護的工作原理,需要了解相互關聯的各個機制。下面會介紹在80386特權級保護中起到關鍵作用的:段描述符和描述符表保護模式下的內存訪問方式特權級的不同維度特權級校驗規則等內容。

二、段描述符和描述符表

  在8086中,為了能夠讓程序在內存中浮動的加載裝配,通過段地址和段內偏移地址共同組成最終的物理地址。而在保護模式下,段機制依然存在,只不過為了支持特權級保護,在一個段能夠被訪問之前,需要先進行"登記注冊"。

  用於登記注冊一個段詳細信息的數據結構被稱為段描述符(Segment Descripter),固定占8個字節,64位。在一個多任務系統中會定義很多不同的段,一個段就對應着一個段描述符,為了統一的進行管理,需要在內存中開辟一段連續的空間集中存放緊密相連的描述符,而這一段連續的內存空間被稱為描述符表。(除了段描述符,還有門描述符等其它類型的描述符)

  描述符表有多種類型,如全局描述符表(Global Descripter Table GDT)、局部描述符(Local Descripter Table LDT)、和中斷描述符表 (Interrupt Descriptor Table IDT)等

段描述符

  段描述符占8個字節,共64位。其結構如下圖所示,上半部分是高32位、下半部分是低32位。

(截圖自 x86匯編語言 從實模式到保護模式)

段基地址

  段基地址是一共32位的線性地址。被分為了物理上不連續的兩段是因為之前的80286保護模式尋址是24位的,而為了和80286的兼容便只能在之前的段描述符設計上進行擴充。

  在沒有開啟頁機制的情況下,這32位的段基地址就是最終的物理地址。

段界限

  段界限一共20位。被分為兩段的原因和段基地址一樣,都是為了兼容之前80286的48位段描述符。段界限用於控制段的拓展范圍,在訪問時,段內偏移超過段界限時會引發錯誤。

  對於向上拓展的段,如數據段、代碼段來說,偏移量從0開始遞增,段界限決定了段內偏移量的最大值。對於向下拓展的段,如棧段來說,偏移量從0開始遞減,段界限決定了段內偏移量的最小值。

  有了段界限這一限制,在使用訪問段時,就能及時發現程序中對內存段訪問的越界行為。更重要的是,段界限的限制能夠防止程序越界,訪問原本不屬於該程序,不能被其訪問的內存空間。

G位

  G位(Granularity)粒度位,用於解釋段界限的含義。

  段描述符中的段界限是20位的,咋看之下一個段的最大空間似乎最大只能是為20位即1MB,這和80386的最大尋址空間4GB可不太匹配。

  20位的段界限是80386的設計者為了兼容20位尋址空間而采取的兼容性設計,而G粒度位便是其中的關鍵。當G位=0時,段界限以字節為單位,段的最大界限就是(2^20)*1B=1MB;當G位=1時,段界限以4KB為單位,段的最大界限擴展為(2^20)*4KB=4GB,和80386的最大尋址范圍相匹配,此時段界限最小也是4KB。

S位

  S位(Descriptor type)類型位,用於標識當前段的類型。

  當S=0時,表示當前段為系統段;當S=1時,表示當前段是一個代碼段或數據段(棧段也被視為特殊的數據段)。

DPL字段

  DPL字段(Descriptor Privilege Level)代表當前段描述符的特權級,占兩bit位。

  80386共支持4種特權級級別,分別是0,1,2,3。數字越小,特權級越高,0代表最高特權級,3代表最低特權級。

P位

  P位(Segment Present)段存在位,用於標識當前段是否存在於內存中。

  P=0時,代表段在內存中不存在;P=1時,代表段存在於內存之中。通常段描述符所指向的段總是位於內存中的。但有時物理內存空間緊張時,可能只是預先構造了段描述符,而沒有分配使用對應的內存空間;或者有的操作系統會在內存緊張時將不常用的段暫時的交互到磁盤中,騰出內存給當前正在執行的程序,以實現段式虛擬內存。

  上述情況下,段描述符的P位就應該被置為0。CPU在對段描述符所指向的段進行訪問時,會對P位進行校驗。當發現P=0時,CPU會發出一個異常中斷,操作系統應該提供對應的中斷處理程序用於將對應內存段從磁盤中置換入內存中,同時將對應段描述符的P位置為1。

TYPE字段

  TYPE字段共4位,用於表示段描述符的子類型。

  (截圖自 x86匯編語言 從實模式到保護模式)

  對於數據段來說,TYPE的4位分別是X/E/W/A位,而對於代碼段來說,TYPE的4位分別是X/C/R/A位。其中X、A位的含義是相同的,而中間的兩位由於數據段和代碼段的性質不同,被賦予了完全不同的含義。

既然都是內存段,如何判斷一個段是數據段還是代碼段呢?

  事實上,一個段是數據段還是代碼段並不取決於定義時的段描述符,而取決於當前段被用於何種場景。

  如果被加載在CS代碼段寄存器中,便被視為代碼段;如果被加載在DS、ES、SS等數據段寄存器、棧段寄存器中,則被視為數據段。

TYPE字段各bit位介紹

  X(eXecutable)位標識當前段是否可執行。數據段總是不可執行的,因此X始終為0;代碼段總是可執行的,因此X始終為1。

  A(Accessed)位標識當前段是否已被訪問。在段描述符初始化時,應該被設置為0。當對應段被訪問時,由CPU硬件將A位設置為1。操作系統在設計虛擬內存管理時,通常需要通過某些算法策略決定應該將哪些內存移入磁盤。段描述符中的A位可以幫助操作系統判斷某一段時間內,對應段內存是否被訪問過,為置換算法提供一定的依據。A位的清零操作也由操作系統來負責。

  E(Expand)位標識數據段的拓展方向。E=0代表數據段是向上,向高地址方向拓展,一般的數據段都是向上拓展的。E=1代表數據段是向下,向低地址方向拓展,這里主要指的就是棧段。因為入棧時,棧頂指針是自減的。

  W(Writeable)位標識數據段是否可寫。數據段總是可讀的,但是不一定可寫 。W=0時,代表當前數據段不可寫,如果對當前段有寫入指令執行時,會引發CPU異常中斷。W=1時,數據段可讀可寫。

  C(Confirming)位標識代碼段是否是特權級依從的。C=0表示當前代碼段是非依從的,意味着當前代碼段只能被相同特權級的程序調用(或者門調用);C=1表示當前代碼段是依從的,意味着當前代碼段允許被更低特權級的程序調用。

關於特權級依從和門調用的概念,會在后面進行進一步介紹。

  R(ReadAble)位標識代碼段是否是可讀的。代碼段總是可以執行的,但80386不允許對一個代碼段進行寫入(如果要對代碼段中的內容進行修改,應該改用一個可寫的數據段指向對應的內存空間 (別名))。同時,80386還對代碼段的可讀性做了限制。R=0時,代表該代碼段是不可讀的,尋址R=0的代碼段將會引發處理器異常中斷;R=1時,代表該代碼段是可讀的。這里的不可讀,不是指CPU無法讀取內存中的指令內容,而是用於限制程序軟件的行為,例如在內存尋址中使用段超越前綴"CS:"來尋址訪問代碼段中的內容。

全局描述符表GDT

  描述符表中最核心的是全局描述符表。從名稱中的全局二字可知,全局描述符表在80386CPU運行時是為整個軟硬件提供服務的,只能存在一個。在進入保護模式前,需要事先定義好全局描述符表。

  為了讓CPU能夠在訪問段的時候隨時定位並讀取到全局描述符表中的數據,80386提供了全局描述符表寄存器(Global Descripter Table Register GDTR)。

  GDTR是48位的,高位的32位用於存放全局描述符表的起始線性地址,低位的16位用於存放GDT的界限。對GDTR賦值的匯編命令為:lgdt  m16&32。 

  32位的起始線性地址,最大可尋址4GB,意味着理論上GDT可以被定義在內存的任意位置。由於進入保護模式前需要先定義GDT,因此GDT一般被設置在實模式下可尋址的1MB內的低地址(進入保護模式后也可重新定義GDT)。

  16位的界限值最大為64KB。由於描述符大小為8個字節(8B),因此GDT中可以存放的描述符的上限為2^10^8=8192個,這在多數情況下是綽綽有余的。

局部描述符表LDT

  現代的操作系統是多任務的,那么什么是任務呢?

  程序是保存在存儲介質中的指令和數據的結合體,而正在執行程序的一個副本就是任務。一個程序可以有運行在內存中的多個副本,每個副本都是一個任務。支持多任務的操作系統要讓並發的多個任務和諧共處,就需要在任務之間通過一些手段令不同任務間彼此隔離。一般情況下,不同任務之間的內存是不互通的,每個任務都有自己的內存空間,一個任務不能隨意訪問另一個任務獨有的內存空間。

  80386CPU的設計者建議為每個任務分配獨有的描述符表,這一描述符表被稱為局部描述符表LDT。每個任務私有的內存段,其段描述符不放在GDT中,而是放在LDT中。

  CPU通過可以通過GDTR寄存器來找到GDT所在的位置。同樣的,80386提供了局部描述符表寄存器(Local Descripter Table Register LDTR)來追蹤LDT的位置。

  和GDT不同的是,每個任務都有着自己的LDT。在多任務輪流執行的多任務系統中,正在執行的任務被成為當前任務,而LDTR則指向當前任務的LDT。任務切換時,LDTR中的數據會發生變化,指向新的當前任務。

三、保護模式下的內存訪問方式

  前面介紹了段描述符和GDT,下面說明80386CPU在尋址時是如何與段描述符、GDT交互的。

  在8086中,段寄存器存放的是段基址。CPU進行內存尋址時,16位的段基址左移4位與16位的偏移地址相加生成最終的物理地址。而在80386中,尋址方式發生了一定的變化。

  80386中,段寄存器被擴展為了兩部分。實模式下,80386段寄存器只有16位的前半部分參與工作,使用方式和8086的16位段寄存器無異,可以兼容的運行8086程序。而在保護模式下,使用段寄存器進行內存尋址的方式發生了變化。此時,前半部分16位裝載的不再是段基址,而是一種被稱為段選擇子的數據結構,保護模式下段寄存器的前半部分被稱為段選擇器;后半部分用於存儲所加載的段描述符相關數據,被稱為描述符高速緩沖器。

  此外,80386在8086的段寄存器CS/DS/ES/SS的基礎上,還新增了兩個數據段寄存器FS和GS,為復雜匯編程序的開發提供了更好的支持。

  (截圖自 x86匯編語言 從實模式到保護模式)

 什么是段選擇子?

  前面說到,保護模式下段寄存器中裝載的不再是段基址,而是段選擇子。但CPU從本質上來說依然是通過段基址+偏移地址進行內存尋址的,只不過在中間引入了一層段選擇子的抽象,用於實現特權級保護。

  段選擇子是一個16位的數據結構,由三部分組成:占高13位的段描述符索引、TI(Table Indicator)描述符表指示器以及RPL(Request Privilege Level)請求特權級。

   (截圖自 x86匯編語言 從實模式到保護模式)

描述符索引

  段描述符索引用於在段描述符表中定位對應的段描述符。如果將段描述符表看作一個結構體數組,那么描述符索引就是數組的下標。

  假如段寄存器加載的是描述符表中第3個段描述符所對應的段,那當前段描述符索引的值就是0000000000010(下標從0開始)。段描述符表所能容納的最大描述符個數是8192個,13位的描述符索引恰好能夠與之一一對應。

描述符表指示器TI

   TI用於標識當前段描述符位於何種描述符表中。TI=0時,表示當前段位於GDT中;TI=1時,表示當前段位於LDT中。根據TI的不同,在加載段選擇子時,CPU將會去訪問對應的段描述符表,根據描述符索引獲取對應的段描述符信息,加載到段寄存器中。

  由於段選擇子中並沒有段基址、段界限等內存尋址時的關鍵數據,這些數據都只在段選擇子指向的段描述符中。但每次使用段寄存器尋址時,不能總是通過段描述符表獲取段描述符數據,頻繁的內存尋址效率太低。

  因此,80386的設計者在段寄存器中設置了描述符高速緩沖器。只有當段選擇子變化時,段寄存器才需要訪問一次描述符表,獲取對應的段描述符數據,將其存入描述符高速緩沖器中。之后,對於當前同一段選擇子的訪問,便可以直接從描述符高速緩沖器中獲取數據,極大的提高了CPU通過段寄存器進行內存尋址的性能。

  描述符高速緩沖器和存儲器高速緩存一樣,是純硬件控制的,無法通過程序直接訪問、修改其中的數據。

請求特權級RPL

  RPL請求特權級,標識着提供段選擇子的程序的特權級別。RPL的作用很難單獨拎出來說明,會在接下來的段特權級訪問保護機制中進行介紹。

GDT和LDT的關系

  為了更好的保護每個任務的LDT,防止其被其它任務隨意訪問,需要將每個任務的LDT視為一個需要進行特權級保護的段,將每個任務的LDT都注冊到GDT中。

  每個任務的LDT,都有一個在GDT中的段描述符與之對應(在段描述符中S位=1的系統段)。80386CPU的LDTR被設計為16位,其中存放的是對應LDT的段選擇子,當任務切換時,只需切換LDTR中的段選擇子即可。

  通過段選擇子尋找對應段時,如果段選擇子中的TI=0,代表所要尋找的段描述符在GDT中。CPU根據GDTR中的數據,找到GDT,並按照下標計算偏移量,獲取對應的段描述符。如果段選擇子中的TI=1,代表所要尋找的段描述符在LDT中,CPU先根據當前LDTR中的段選擇子去GDT中尋找對應的LDT段描述符,從中獲取當前LDT的線性基地址。接着,再通過所請求的段選擇子中的描述符索引在LDT中查找最終所需的段描述符

四、特權級的三個維度

  前面介紹了許多用於實現特權級保護的機制,現在終於可以開始說明80386究竟是如何利用這些機制來完成特權級保護的。

  特權級保護從本質上來說,是保護高特權級的內存、外設等資源不會被沒有權限(低權限)的程序訪問。主體結構是程序訪問資源,而CPU需要在這個過程中進行特權級的校驗。

  這里引入三種不同概念的特權級:當前特權級CPL、描述符特權級DPL、請求特權級RPL

CPL當前特權級(Current Privilege Level)

  CPL當前特權級,用於表示當前所運行程序的特權級。更進一步的說,也就是當前CS代碼段寄存器中所裝載段選擇子的后兩位所決定的特權級。

  BIOS在加載操作系統並進入保護模式時,處理器會在執行第一條指令時自動的將CPL設置為0,可以看做操作系統在進入保護模式時擁有的最高CPL是從處理器繼承而來。之后便由操作系統程序負責整個計算機系統的管理,例如加載用戶應用程序時,將用戶程序的CPL設置為最低特權級3。應用程序雖然不希望自己被放在最低特權級,但操作系統主導了應用程序的加載,其所使用的段描述符、LDT等都由操作系統創建和管理,應用程序只能專注於自己的業務功能,無權控制自己的CPL當前特權級。

  特權級分為4種,可以被看做幾個不同大小的同心圓,像一個個的指環,特權級0也被稱為ring0。因此運行在核心處ring0特權級的操作系統程序,也被稱為內核(Kernel)程序。

  (截圖自 x86匯編語言 從實模式到保護模式)

特權指令

  80386CPU提供了一系列的機制實現特權級保護,如段描述符、GDT等等。全局描述符表GDT是特權級保護機制中的一個關鍵要素,通過指令lgdt可以進行GDT的設置。可如果本應該被高權限的操作系統管理起來的低特權級程序(CPL=3)也能執行lgdt指令的話,整個特權級保護機制就像一幢宏偉的高樓被抽離了地基,變得脆弱不堪。

  80386的設計者自然不會設計出這種百密一疏的方案。因此,在整個80386的指令集中,其中很多底層的、權限很大的指令被規定只能被最高權限的程序(例如操作系統)執行。讓CPU停機也是通過指令來完成的,如果應用程序也能隨意的執行停機指令,那將是非常恐怖的事情。

  只有處於最高當前特權級CPL=0的程序才有權限執行的指令,叫做特權指令。其中主要包括停機指令;加載GDT、LDT的指令;讀寫控制寄存器的mov指令等等。

DPL描述符特權級(Descriptor Privilege Level)

  DPL目標特權級,用於標識所指向目標的特權級。前面提到過,每個段描述符都有DPL字段屬性。DPL特權級的高低,決定了能夠被位於何種特權級的程序所訪問。

  CPU對內存段訪問的特權級保護是在段選擇子加載的時進行的。當有新的段選擇子准備加載到段寄存器時,CPU會根據段寄存器的類型進行相應的校驗。

代碼段訪問的保護機制

  對於代碼段寄存器的來說,加載新的段選擇子可能意味着CPL的變化,校驗比較嚴格,段間控制轉移一般只允許發生在相同特權級的程序之間。也就是說,一個當前特權級CPL為2的程序,只能跳轉到另一個特權級DPL同樣為2的代碼段執行,而無法跳轉到特權級DPL為0、1、3的代碼段。一般程序內部相同特權級代碼段間互相跳轉都是沒問題的,但還存在一些場景需要允許低特權級的程序去調用高特權級的代碼:例如低CPL的應用程序去調用高DPL的系統調用例程。

有兩種方法允許低CPL的程序跳轉高DPL的代碼段:

  一是將高特權級的目標代碼段定義為依從的,也就是將代碼段描述符中TYPE字段的C位設置為1,代表當前代碼段是特權級依從的。當低特權級的程序跳轉至高特權級的代碼段時,CS的后兩位不發生變化,CPL和調用程序保持一致。

  二是通過門來進行,門(Gate)也是一種描述符,被稱為門描述符。門描述符區別於段描述符,段描述符描述的是一個段,而門描述符描述的是一段可執行的代碼、一個程序或者一個任務,系統調用通常使用門描述符來實現。使用jmp far指令可以將控制通過門調用轉移到高特權級代碼段,但是依然不改變當前特權級CPL;使用call far指令則在將控制轉移到高特權級代碼段的同時,還會將當前特權級CPL提升到和目標代碼段DPL一致,也就是說,一個CPL為3的應用程序,通過門調用調用到了一個DPL=0的代碼段程序,則CPU將會將當前特權級CPL提升為0,和目標代碼段特權級保持一致。

數據段訪問的保護機制

  數據段訪問的保護機制相對來說簡單一些:處於低CPL的程序無法訪問高DPL的數據段。換句話說,在訪問數據段前,向數據段寄存器(DS/ES/FS/GS)載入新的段選擇子時,要求當前CPL必須高於或等於目標段DPL(數值上CPL <= DPL)。

  舉個例子,在古代等級森嚴的封建制度下,皇帝可以認為是位於CPL=0的級別,普天之下莫非王土,皇帝可以在自己的國家訪問任何它想要訪問的領地(數據段)。但反過來位於CPL=3的平民是沒法去直接訪問皇宮的(DPL=0的數據段)。位於中間級別的CPL=1、2的程序就像地方諸侯,CPL=1的諸侯雖然也無法直接訪問皇宮,但對於自己的寢宮(DPL=1的數據段)和平民的家(DPL=3的數據段),都是有權利訪問的。

  特別的,為了避免高特權級的程序由於棧空間不足而崩潰以及阻止不同特權級間棧數據的交叉引用,處理器在特權級變化的時候,堆棧也會跟着發生變化。所以,向棧段寄存器SS載入新的段選擇子時,要求當前CPL必須完全等於目標的DPL(在數值上CPL = DPL)

  到了這里,看起來80386的內存保護機制似乎已經很完善了。高特權級ring0的操作系統和低特權級ring3的應用程序彼此之間被特權級保護機制隔離開了,低特權級的應用程序不能隨意的訪問高特權級的操作系統內存;沒有提供對應的門描述符或者依從代碼段,應用程序也無法調用高特權級的程序。

  那么80386的設計者提供的請求特權級RPL作用又是什么呢?

RPL請求特權級(Request Privilege Level) 

  RPL請求特權級,代表請求者的特權級。在執行段間控制跳轉指令時,需要提供目標代碼段的選擇子,載入CS代碼段寄存器;在訪問數據段時,也需要將數據段選擇子裝載入DS、ES等數據段寄存器中。無論是執行控制轉移,還是訪問數據段,都可以看作是當前執行任務的一個請求,RPL也就是當前請求者的特權級。

  大多數情況下,請求者就是當前任務,因此CPL=RPL。誰負責提供段選擇子,誰就是請求者。但在某些時刻,提供段選擇子的請求者和當前任務並不相同。

  我們知道使用call far轉移指令調用操作系統提供的調用門執行系統調用時,會將CPL從應用程序的ring3提高到操作系統所處的ring0。

  假如操作系統提供了一個系統調用,用於從磁盤中讀取數據,並將其寫入到應用程序數據段的指定位置中(由於系統調用中可能會執行一些特權指令,或是外設被限制了訪問特權級,所以通過調用門call far時會提升CPL)。這個系統調用有三個參數:磁盤的扇區號(指定從磁盤的什么位置讀取),需要寫入的數據段的段選擇子(指定寫入哪一數據段),最后一個是數據段的段內偏移地址(用於更精確的控制寫入數據的段內位置)。

  這個系統調用的設計看起來還不錯,能讀取指定磁盤扇區的數據並寫入指定數據段中,但卻隱藏了一個嚴重問題

  如果應用程序的編寫者是一名惡意的攻擊者,他給出的數據段選擇子參數指向的不是應用程序自己的數據段,而是操作系統的數據段選擇子。雖然只有CPL=0的程序才有權限訪問操作系統設置的DPL=0的內核數據段,但是由於通過call far調用門進行系統調用時,會將CPL提升為0,因此這個操作會被允許執行。這是一個很嚴重的漏洞,通過call far調用門實現的系統調用,模糊了CPL和事實上的請求者特權級的關系,使得只有CPL、DPL的校驗機制在這種情況下顯得無能為力。CPU很難區分出在段選擇子的加載時,這個段選擇子究竟是操作系統提供的還是惡意應用程序提供的。

  因此,80386的設計者在CPL、DPL的基礎上又提供了RPL請求特權級來解決這個問題。雖然CPU不知道段選擇子的提供者是誰,但操作系統是知道的。操作系統在內核中訪問內存段時,請求者自然是操作系統自己;而操作系統提供系統調用為應用程序服務時,也能明確知道請求者是低特權級的應用程序。

  在上述磁盤讀取系統調用的例子中,操作系統可以在系統調用的程序中修改應用程序提供的段選擇子的RPL,將其設置為和應用程序匹配的低特權級后再送入段寄存器中。CPU在校驗時,除了要求CPL高於或等於目標段的DPL(數值上CPL <= 目標DPL),也要求給出的段選擇子中的RPL也必須高於或等於目標段的DPL(數值上RPL <= 目標DPL)。

  引入了RPL后,並且操作系統在系統調用中合理的設置了段選擇子的RPL,上述漏洞就不復存在了。正常的應用程序能夠訪問磁盤數據,並正確的寫入自己的數據段中(CPL=0 RPL=3,目標DPL=3 校驗通過);但惡意的應用程序即使傳入的段選擇子RPL=0,也會被系統調用給重置為RPL=3,非法訪問操作系統內核數據段的企圖將會被CPU發現,引發異常中斷(CPL=0 RPL=3,DPL=0 校驗不通過)。

五、內存特權級保護校驗規則

  前面的舉例分析中,或多或少的介紹了幾種內存訪問時的特權級保護校驗規則,這里系統的總結一下。內存特權級保護的規則根據內存段的性質不同,有一定差異,分情況討論。

代碼段特權級校驗規則

  非特權級依從代碼段直接轉移: 直接控制轉移到非特權級依從的代碼段時,要求當前特權級CPL、請求特權級RPL都等於目標代碼段DPL(數值上CPL = 目標代碼段DPL,RPL = 目標代碼段DPL)。

  特權級依從代碼段直接轉移:直接控制轉移到特權級依從的代碼段時,要求當前特權級CPL、請求特權級RPL都低於或等於目標代碼段DPL(數值上CPL >= 目標代碼段DPL,RPL >= 目標代碼段DPL)。

  門描述符特權轉移:通過門描述符進行的控制轉移規則較為復雜,在后續關於門描述符的博客再對門描述符控制轉移的規則再進行展開介紹。

數據段特權級校驗規則

  CPU允許高當前特權級的程序訪問低特權級別的數據段。換句話說,低當前特權級的程序無法訪問高特權級的數據段。

  即要求當前特權級CPL,請求特權級RPL都必須高於或等於目標數據段DPL(數值上CPL <= 目標數據段DPL,RPL <= 目標數據段DPL)

棧段特權級校驗規則

  CPU要求任何時候,訪問的棧段特權級必須和當前特權級CPL相一致。

  即要求當前特權級CPL,請求特權級RPL都必須等於目標棧段DPL(數值上CPL = 目標棧段DPL,RPL = 目標棧段DPL)。

六、I/O特權級保護

  一般來說,操作系統是不允許應用程序有權限直接訪問外設的,而是通過提供系統調用的方式對外設的訪問進行保護。在標志寄存器EFLAGS中,第12、13位是IOPL位(I/O Privilege Level),代表着當前任務的I/O特權級,只有當前特權級為0的任務才能修改標志寄存器中的IOPL位。IOPL共兩位,對應着特權級的四個級別。當前特權級CPL高於或等於IOPL的時候(數值上CPL<=IOPL),便允許當前任務訪問外設。通過IOPL,操作系統便能按照需要對不同的應用程序進行外設訪問的控制。

  (截圖自 x86匯編語言 從實模式到保護模式)

  但有時操作系統並不希望徹底的阻止某一應用程序對外設的訪問,而是能選擇性的對應用程序開放部分I/O端口的訪問權限。因此80386還提供了I/O訪問位圖用於更細粒度的外設訪問權限控制。

  80386最多可以訪問2^16共65536個外設端口。對應的,I/O訪問位圖中按照從零開始順序的每一bit位標識了當前任務是否有權限訪問對應的I/O端口,第一個bit位代表端口0,第二個bit為代表端口1,以此類推,最大可以為65536位,占8Kb空間。

  I/O訪問位圖中的bit位為0代表對應端口有權限,bit位為1代表對應端口無權限。當CPU發現CPL低於IOPL時,並不會直接發生異常,而是會進一步嘗試着查詢當前任務的I/O訪問位圖,只有在I/O訪問位圖中也發現不滿足條件時,才產生異常。通過I/O訪問位圖,操作系統可以靈活的設置應用程序的I/O端口訪問權限,選擇性的向其開放所需要的接口權限。

  需要注意的是,端口是面向字節編址的,每個端口僅被設計為一次讀寫一個字節的數據。當以字、或者雙字進行端口訪問時,實際上是訪問連續的2個或4個端口。當從端口n讀取一個字,即兩個字節時,相當於從端口n和端口n+1中各讀取一個字節的數據。因此,當CPU進行字、雙字的I/O指令時,會同時檢查相關的2個或4個連續的端口。只有當I/O位圖中對應的所有的端口標識都為0時(有權限),才通過權限校驗。

七、總結

  在通過學習《x86匯編語言 從實模式到保護模式》以及有關內容的博客,掌握了一定的80386硬件及匯編知識后,我才具備了閱讀、學習ucore操作系統源碼的基礎。雖然《x86匯編語言 從實模式到保護模式》的作者在很多地方都很體貼的站在初學者的角度來講解原理,但一方面由於自己在閱讀時不夠專注,另一方面也和涉及到的知識點繁多且關系緊密有關。對我而言,單純通過閱讀學習的效果並不是特別理想。 

  《暗時間》一書中提到了兩個很有價值的觀點:書寫是為了更好的思考、教是更好的學。在寫博客的過程中,會不自覺的反復思考並總結所寫的內容,找到那些自以為了解但事實上卻理解不夠深刻的內容。同時假設有一個虛擬的初學者會閱讀所寫的博客,換位思考這個虛擬的初學者可能遇到的問題,盡可能的將內容以淺顯易懂的方式說清楚。當然,如果博客能幫助到和我一樣,對CPU硬件、操作系統原理感興趣的小伙伴就更好了。

  對於學習基於x86保護模式的ucore乃至流行的Linux、Windows等操作系統來說,x86CPU的特權級保護機制是至關重要的一部分。有了硬件級別的特權級保護機制,才能實現多任務的隔離以及各種軟件級別的權限控制。后續的80386學習相關博客將會包括門描述符的詳細介紹以及分頁機制、虛擬內存管理等相關的知識點,這些內容都是學習ucore操作系統公開課時所必須的。

  作為一個初學者,博客中如有錯誤或者理解不到位的地方,還請指正。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM