我們一起學習實模式和保護模式
今天我們就系統的講下實模式和保護模式。我覺得能很形象的說明保護模式存在的意義。先看下面這段代碼。
int main()
{
int* addr = (int*)0;
cli(); //關中斷
while(1)
{
*addr = 0;
addr++;
}
return 0;
}
這段代碼如果能順利執行,其實是很可怕的。你會發現他直接的獲取到了內存的 0 號位置,并且能順序的向下遍歷,然后還能直接的對內存寫入數據,對了,這段代碼還把中斷關了。想象一下,如果你和幾個用戶一起用一臺服務器,一旦有個用戶執行完這段代碼后,那么所有人還在內存中的數據將被刪的干干凈凈,并且在刪的過程中還沒有任何辦法去中斷這段代碼對應的進程。
這段代碼的工作方式就是實模式的工作方式。實模式的特點主要在于實模式的尋址方式,實模式的尋找方式進而決定了實模式在尋址范圍以及保護性上都不如保護模式好。目前實模式的存在主要是為了兼容之前的系統,在操作系統讀入bootsect.s 以及執行 bootsect.s 進行 setup 和 system 讀入階段是在實模式下執行,之后都是在保護模式下執行了。我們接下來分別看實模式和保護模式的工作方式。
Part1實模式
今天詳細講一下載入過程。正好我們來感受下實模式是如何工作的。
首先說實模式應該如何表示指令在內存的位置,實模式下通過 ”CS“ 和 ”IP“ 兩個寄存器來表示位置,CS 寄存器存段基指,IP 寄存器存偏移。這里簡單介紹下為什么要用段表示。了解匯編的同學可能知道匯編代碼中會把代碼分成很多段,有代碼段、數據段等。這樣程序在載入內存時也是分成不同的段載入內存的。這樣取指令時就需要找到段的基址,然后再確定偏移,就能得出指令的地址。具體方式是把 CS 寄存器中的值取出來向左偏移 4 位,然后加上 IP 偏移的值。比如要得到 BIOS 的地址,那么從CS寄存器得到的段基址就是 0xFFFF,IP 取出的偏移是 0x0000。CS偏移 4 位加 IP 就是 0xFFFF0(地址的表示是16進制的,但左移四位說的是二進制的偏移,十六進制偏移一位和二進制偏移四位效果是一樣的)。這樣,BIOS的位置就找到了,接下來取出指令并執行就好了。
CS寄存器中的值取出來后先偏移 4 位就是因為 CS 寄存器和 IP 寄存器都是16位的,16位只能表示 64k 的大小。將 CS 向左移4位加上偏移,就能用20位表示地址了,尋址范圍能達到 1M。
雖然通過將從CS寄存器中取出的段基址左移四位后尋址范圍增加了,達到1M,但對于現代的計算機來說尋址范圍實在太小了(就算是32位的計算機 CS 寄存器也是16位的),這就是實模式下的一大缺點。我們再看看保護模式是怎么做的。
Part2保護模式
對于 32 位的計算機,如果內存尋址只能尋到 1M,顯然太小了。理論上,32 位的計算機適配的地址線也有 32 位,我們應該以 32 位的地址去表示地址。但是為了適配之前的系統(現在的操作系統在加載 BIOS,用 BIOS 執行載入 bootsect.s 等一直用著上面所表述的實模式的尋址方式),CS 這個段寄存器一直就是 16位 的,只是存偏移的那個寄存器多了一個叫 EIP 的 32 位的寄存器。為了讓尋址適應這個變化,CS 寄存器存的不再是真實的段基址了,而是段選擇子,其中段選擇子的 0-2 位用作權限控制,3-15 記錄的是 GDT 表的索引。
拿到段選擇子后就可以通過選擇子找到段描述符,然后再通過段描述符(GDT表)找到相應的段基址,然后再把段基址和偏移相加就是真實的地址了。
這個 GDT 表可是有大用,不光解決尋址的問題,對于內存的保護也可以靠他。
這個段描述符中段長和段基址都是斷開的,這是由于歷史原因構成的,咱們還是主要看段描述符中的內容以及對應的功能。段長度可以限定段內偏移地址,也就是說你拿到一個段基址后只能在這個界限內偏移,如果偏移超過限定值訪問就不合法了。段基址就和實模式下的段基址一樣,只是現在有了32位的段基址。描述方面主要是對段的可讀可執行,代碼段還是數據段進行描述,另外描述中有一個相當重要的就是 DPL,這個 DPL 限定了權限級別,只有段選擇子中記錄的發起訪問者的訪問權限滿足 DPL 的限定,才能訪問。
不難看出,GDT 表對相應的段的可讀可寫以及段是代碼段還是數據段等情況都做了詳細的描述,每次通過段選擇子找到段描述符時,都會判斷能不能訪問成功。
下面通過流程圖看下保護模式的完整取指過程。