保護機制是多任務環境和系統能夠運行的一種基礎,它能夠保護任務獨立運行,免受其他任務的干擾。在 80x86 設計中,在分頁機制和分段機制下使用了保護機制。例如分段過程中有虛擬內存的保護,能夠保證應用程序在訪問兩個不同的任務下不會相互干擾;另外還有段和寄存器之間的保護,通過定義優先級來判斷應用程序是否具有訪問指定段和寄存器的權限,而分頁里面由于有頁目錄和頁表結構的存在,這個結構中有 R/W 和 U/S 位,也可以提供訪問和寫入保護。
下面我們就來詳細聊一下保護機制,即分段對應段級保護,分頁對應頁級保護。
段級保護
在保護模式下,80x86 提供了段級保護和頁級保護。這種保護機制根據特權級提供對段和頁的訪問能力,段保護是 Level-4 級保護,頁保護是 Level-2 級保護。操作系統代碼和數據存放在要比普通應用程序具有高特權級的段中。此后處理器的保護機制將會限制應用程序按照指定特權級的權限來訪問。
使用保護機制時,每個內存引用都會被檢查用來驗證內存引用的保護要求,如果符合內存保護要求的話,就會執行地址轉換操作,這種檢查 - 執行的操作很像我們平常寫代碼的(下述為偽代碼)
if(expression){ ??... }else{ ??... }
檢查和執行操作是同時進行的,因此性能不會受到太多影響。
說到保護機制的檢查,下面共有幾種檢查方式:
段限長檢查;
段類型檢查;
特權級檢查;
可尋址范圍限制;
過程入口點限制;
指令集限制。
如果違反上面任意一種檢查操作都將導致一個異常產生,下面我們就來具體聊一下這些檢查機制都是干什么的。
段限長檢查
還記得段描述符嗎?這段描述能夠比較形象的說明段描述符的作用:
段描述符是 GDT 和 LDT 表中的一個數據項,用于向處理器提供有關一個段的位置和大小信息以及訪問控制的狀態信息。
段描述符能夠向處理器提供段的位置和大小等相關信息,每個段都由段基址(BASE)、段界限(LIMIT)和段屬性組成,這表明段是有限制的。
段限長檢查也就是對段界限/段限長進行 Limit 檢查,它會限制應用程序防止其尋址到段外內存位置。段限長的有效值會依賴于顆粒度標志 G 的設置狀態,如果是數據段的話,段限長還與 E 標志(擴展方向)、B 標志(默認棧指針大小/上界限)有關。
顆粒度標志 --- G
這個字段用于確定段限長字段 Limit 值的單位,如果顆粒度標志為 0 ,則段限長值的單位是字節;有效范圍是 20 位段描述符中段限長字段 Limit 的值,Limit 的范圍從 0 - 0xFFFFF(1MB)。
如果設置了顆粒度標志,則段限長使用 4KB 單位,這時候 Limit 需要乘以顆粒度標志,有效范圍是從 0 - 0xFFFFFFFF(4GB)。
這里需要注意:段偏移地址的低 12 位不會進行 Limit 檢查。
擴展方向 --- E
段有兩種擴展方式,一種是向上擴展,一種是向下擴展。根據擴展方向 E 的不同,處理器會以不同的方式使用數據段;對于向上擴展的數據段(簡稱上擴段),邏輯地址中的偏移范圍從 0 - 段限長 Limit 。大于 Limit 的偏移值會產生異常;對于向下擴展的數據段,段限長 Limit 的含義相反。根據默認棧指針大小標志 B 的設置,偏移范圍可從段限長 Limit 到 0xFFFFFFFF 或 0xFFF。而小于段限長 Limit 的偏移值會產生一般性保護異常。對于下擴段來說,減小段限長字段中的值會在該段地址空間底部分配新的內存。由于 80x86 是向下擴展的,因此這種方式很適合擴展堆棧。
D/B --- 默認操作大小/默認棧指針大小和/或上界限 ?Default operation size/default stack pointer size and/or upper bound
根據段描述符表示的是可執行代碼段、下擴數據段還是堆棧段,這個標志具有不同的功能(如果是 32 位,這個標志應該設置為 1,16 位應該設置為 0 )。如果是可執行代碼段時,這個標志是 D 標志;如果是棧段和下擴數據段,這個標志是 B 標志;
除了下擴數據段以外的所有類型,有效 Limit 的值是段中允許被訪問的最后一個地址,它比段長度小 1 個字節。任何超出段限長字段指定的有效地址范圍都將產生一個保護性異常。
對于下擴數據段來說,段限長具有同樣的功能,但是其含義不同。段限長指定了段中最后一個不允許訪問的地址,因此在設置了 B 標志的情況下,有效偏移范圍是從(有效段偏移 + 1)到 0xFFFF FFFF;當 B 清零時,有效地址范圍從(有效段偏移 + 1)到 0xFFFF 。當下擴段段限長為 0 時,段會有最大長度。
除了對段限長進行檢查,處理器還會檢查段描述符表的長度。GDTR、IDTR、LDTR 寄存器中包含有 16 位的段限長,處理器用它來防止程序在描述符表外面選擇描述符。描述符表的限長值指明了表中最后一個有效字節。因為每個描述符是 8 字節,因此含有 N 個描述符項的表應具有的限長值為 8N - 1。
段類型 TYPE 檢查
除了應用程序代碼和數據段有描述符之外,處理器還有系統段和門兩種描述符類型。這些數據結構用于管理任務以及異常和中斷。但是并非所有的描述符都定義一個段。段描述符在結構中的 S 標志和類型字段 TYPE 含有類型信息。處理器利用這些信息對由于非法使用段或門導致的編程錯誤進行檢測。
當操作段選擇子和段描述符時,處理器會隨時檢查類型信息。主要在以下兩種情況下檢查類型信息:
當一個描述符的選擇子加載進一個段寄存器中。此時某些段寄存器只能存放特定類型的描述符,比如
CS 寄存器中只能被加載進一個可執行段的選擇子;
不可讀可執行段的選擇子不能被加載進數據段寄存器中;
只有可寫數據段的選擇子才能被加載進 SS 寄存器中。
當指令訪問一個段,而且該段的描述符已經加載進段寄存器中。指令只能使用某些預定義的方法來訪問某些段
任何指令不能寫一個可執行段;
任何指令不能寫一個可寫位沒有置位的數據段;
任何指令不能讀一個可執行段,除非可執行段設置了可讀標志。
特權級
處理器的保護機制有四個級別,之前也聊過了,這四個級別從 0 -> 3 依次降低,數值越大,特權級越小。下圖是特權級的四個級別。
在上圖中,Level-0 位于最內側,這一層是內核層,由內核代碼、數據和堆棧組成,由操作系統的內核訪問;Level-0 的外環是 Level-1 和 Level-2 層,這兩層就是由操作系統訪問的邏輯層,最外層是 Level-3 層,由應用程序訪問。
處理器會利用特權級來防止運行在較低特權級的程序或任務訪問具有較高特權級的段,也就是具有特權級為 Level 1-3 的不能訪問 Level - 0 特權級,而 Level-0 特權級可以訪問 Level 1 - 3 ,當處理器檢測到一個違反特權級的操作時,會觸發一個一般保護性異常。
這個好理解,正如你領導的辦公室你沒有權限隨便進一樣,要是給你權限那不就麻煩了嗎?萬一領導正在教訓女員工讓你碰到了可咋整?相反的情況,領導可以隨便進任何一個人的屋子,難道領導進你們小開發的屋子還先敲門申請?那領導不是永遠發現不了你在摸魚嗎?
處理器能夠識別的特權級有三種:
當前特權級 CPL(Current Priviledge Level),這個是當前正在執行程序或任務的特權級。它一般會存放在 CS 寄存器中,也有可能存放在 SS 寄存器中。通常 CPL 等于當前代碼段的特權級。當程序把控制權轉移到另外一個具有不同特權級的代碼段中時,處理器就會改變 CPL 。
描述符的特權級 DPL(Descriptor Priviledge Level),DPL 表示段描述符/門描述符的特權級,存儲在段描述符的 DPL 字段中,當前代碼想要訪問段描述符時,段描述符的 DPL 會和當前代碼段的 CPL 以及段選擇子的 RPL(下面會說明)進行比較,由于段描述符有不同的類型,所以需要分情況討論:
如果是數據段(Data Segment),DPL 只能允許比它自己特權級大的代碼段訪問,也就是說如果 DPL = 1,那么只有當前代碼段等于 0 和 1 才能訪問。
非一致性代碼段(Nonconforming code segment),我在學到這里的時候比較疑惑,什么叫做非一致代碼段,難道還有一致性代碼段?我查閱資料后發現果不其然,一致性代碼段和非一致性代碼段是在段描述符中的 S 位進行區分的,S = 0 表示系統,S = 1 表示代碼或數據。
當 S = 1 時,TYPE 中有四個二進制位(位 8 - 位 11),位 8 -> 位 11 分別是 訪問位、讀寫位、一致位、執行位 ,大家看到了嗎,這個是否表示一致性代碼是由這個位 10 一致位來判斷的。此位置 1 表示一致性代碼,為 0 表示非一致性代碼。
這里解釋下什么是一致性代碼和非一致性代碼:
一致性代碼就是操作系統拿出來可以共享的代碼段,可以被低特權級用戶直接調用訪問的代碼;非一致性代碼是為了避免低特權級的訪問而被操作系統保護起來的系統代碼。
如果某個非一致性代碼的 DPL = 0 ,那么只有 CPL 為 0 的程序才能夠訪問這個段。
調用門(Call Gate)(下述會討論),調用門的 DPL 指出訪問調用門的當前執行程序或任務可處于的最大特權級數值。
一致性和非一致性代碼(通過調用門訪問),其 DPL 指出允許訪問代碼段的程序或任務具有的最小特權級數值。比如代碼段的特權級是 2,那么 CPL = 0 就不能訪問。
任務狀態段 TSS,其 DPL 指出允許訪問 TSS 的當前程序或任務具有的最大特權級數值。
請求特權級 RPL (Request Privilege Level),RPL 是段選擇子的特權級,在段選擇子的位 0 和 位 1,如下圖所示
處理器會同時檢查 RPL 和 CPL 來確定是否允許訪問一個段。如果程序有足夠的 CPL 特權級,那么 RPL 特權級不夠的話也不能訪問。也可以理解為 RPL 的高特權級會覆蓋 CPL 的低特權級。
訪問數據段時的特權級檢查
訪問數據段時會進行特權級檢查,數據段中的段選擇子會存儲在數據段寄存器和堆棧段寄存器中,數據段寄存器就四個,即 DS、ES、FS 或 GS,堆棧段寄存器就是 SS。
把段選擇子加載進段寄存器的指令是 MOV、POP、LDS、LES、LFS、LGS 和 LSS。常見的就是 MOV 和 POP,一般記住這兩個就行。
通過加載指令把段選擇子加載進段寄存器之前會進行特權級檢查,特權級檢查會把當前運行程序的 CPL 、段選擇子 RPL 和段描述符的 DPL 進行比較,如下圖所示。
只有當段的 DPL >= CPU && RPL 時,處理器才會把段選擇子加載進段寄存器中。否則就會產生一個一般性異常,并且不加載段選擇子。
另外,有可能會把數據保存在代碼段中。我們可以通過下面三種方式來訪問代碼段中的數據:
把非一致可讀代碼段的段選擇子加載進數據段寄存器中。
把一致可讀代碼段的選擇子加載進一個數據段寄存器中。
使用代碼段覆蓋前綴 CS 來讀取一個選擇子已經在 CS 寄存器中的可讀代碼段。
在使用堆棧選擇子加載 SS 寄存器時也會執行特權級檢查。這里和堆棧相關的特權級必須與 CPL 匹配。也就是 CPL 、堆棧選擇子的 RPL 和堆棧描述符的 DPL 相同,否則也會產生一般性保護異常。
在切換代碼段時的特權級檢查
處理器會頻繁的執行在不同代碼段之間的切換工作。對于將程序控制權從代碼段轉移到另一個代碼段時,目標代碼段的段選擇子必須要先加載進代碼段寄存器中。當然在加載進代碼段寄存器之前還需要進行特權級檢查工作,emm 必要的工作不能少。
此時的特權級檢查包括對段限長、段類型和特權級進行檢查,檢查沒問題后才會把目標代碼段加載進 CS 寄存器,這樣就會把控制轉移權交給目標代碼段,從 CS:EIP 處開始執行代碼。
把控制轉移權交給目標代碼段固然是一句話就可以描述的事情,但是,,,如何才會使得控制權進行轉移呢?一般有這幾種指令:JMP、CALL、RET、INT 和 IRET,除此之外,異常和中斷機制也是一種實現方式。
編輯:黃飛
?
評論
查看更多