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