我看保護模式

        保護模式,對於學過操作系統的人來說,是一個熟悉而又陌生的概念。之所以熟悉,就是所有的操作系統的書籍都會說到保護模式;之所以陌生,我敢說很多人學完了之後,壓根不曉得什麼是保護模式,怎麼算是保護模式,再者就是它到底保護了什麼東西。

        “你說的不對,怎麼能一棒子打死一幫人!保護模式就是進程相互隔離,實現多任務;就是有分頁機制,可以節約內存。”,暫且不說對與不對,其實我也不知道我理解的是否是正確的。暫且將自己的一點理解記錄一下吧。如果不對,今後發現了,及時糾正吧!閒話少扯,接下來就開始吧。

        現在的計算機的操作系統運行起來之後,無一例外都會進入到保護模式,到底保護模式是什麼,它保護了什麼呢?要說保護模式,先要從實模式說起,更早的話要從8086開始說了。很多東西現在都看不到了,我也沒有仔細研究過。實模式的東西,就把自己看到的一些東西簡單說一下,關鍵在於說保護模式。

        1. 實模式

        最早的個人電腦的CPU是8086,毋庸置疑,它只有實模式,壓根不支持什麼保護模式。8086爲16位的CPU,地址總線爲20位,可以尋址1M,數據總線爲16位,一次存取一個字節的數據,或者一個字的數據。下圖是一個8086的CPU的模型

        

        8086的寄存器都是16位的,它的20位的地址如何獲取呢?學過8086彙編的人都曉得,將段寄存器向左移四位,加上偏移地址,就得到了20位的地址,將地址值通過CPU的地址總線就可以訪問內存的內容了。16位的寄存器只能訪問64K的空間,與1M相比,64k的內存過小,Intel通過 分段尋址的模式,將物理內存訪問範圍擴大到了1M字節。

        然後再說一下80286 CPU,80286現在看可以說是一箇中間產物了,它有實模式和保護模式兩個運行模式。在實模式下,兼容了8086CPU,只能訪問1M的內存。切換到保護模式,它的地址總線是24位,因此它可以訪問16M的內存。但是80286的這個保護模式是一個虛擬保護模式。        

        最後說一下80386 CPU,80386處理器是一個32位的處理器,具有了實模式,保護模式和虛擬8086模式 三個運行模式,完全實現了保護模式。32位的地址總線,可以訪問的內存地址範圍達到 4G;數據總線32位,一次可以訪問4字節的內存。

        保護模式,應該叫做“虛擬地址保護模式”,實現了 內存保護,分段機制保護,硬件虛擬內存存儲等機制。(扯一點閒話:這個保護模式是CPU的機制,還是操作系統的機制呢?CPU的!操作系統的!應該這麼說吧,Intel的CPU支持了保護模式,然後有了操作系統相應的行爲。CPU支持了分頁的地址映射機制,然後操作系統有了分頁的準備工作,並且有了相應的控制軟件/程序)

        留一個問題,CPU可以沒有實模式吧?CPU加電後,直接進入32位的保護模式(所謂的)?

        2. 實模式與保護模式的切換

        爲了兼容之前的CPU,80X86系列的CPU 都有實模式,然後加上其他的模式(保護模式,V8086模式等)。CPU啓動後,首先進入的是實模式(爲什麼?龜腚!),做一系列的檢測。然後進入引導過程,切換到保護模式。(加載內核,初始化內核等等)。那如何從實模式切換到保護模式呢?

        有兩個標誌位用於在兩個模式之間進行切換。

        實模式使用的地址線爲 A0-A19 20位地址線,到了286,386地址線被擴展爲24位和30位,那麼爲了兼容8086 CPU,在286,386及其以後的CPU中,A20 就被稱爲了 A20 Gate,作爲開啓高地址內存的標記(其實是可以訪問更高地址的內存的)。在實模式下A20 Gate被關閉,那麼訪問範圍爲1M內存。到了保護模式下,如果A20 Gate被關閉,對於80386處理器(32位地址線)來說,在訪問內存時,A20地址線就是無效的,只能當作0處理,這樣訪問的地址就被分爲了 00000-FFFFF, 200000-2FFFFF,300000-3FFFFF等一系列的地址段。所以在進入保護模式時,要將A20地址線打開,就可以訪問一系列連續的地址空間了。

        再者,CPU的控制寄存器 CR0的PE位設置爲1,表示CPU要以保護模式運行。

        除了着兩個位以外,還需要爲保護模式設置GDT,LDT等一系列內容,以保證CPU切到保護模式下的正常運行,這個後面來說!

        3. 保護模式

        首先說一下保護模式,保護的含義:

        1. 段基址 / 段界限 定義了一個段範圍,段界限之外的內存禁止訪問

        2. 段屬性對端的定義,規定限制段行爲 和性質

        3. 特權級的轉換,CPL/RPL/DPL 進行比較,將CPU的CPL 與 段的DPL,以及段,門等的RPL靜態定義權限 進行動態的對比,實現系統保護。

        首先說一下保護模式下的內存訪問:

        在8086的實模式下訪問內存,通過 段地址×16 + 地址偏移 的方式獲取20位的地址。而到了保護模式下,段寄存器CS,DS,GS等仍然是16位,而現在的地址總線變爲了32位,顯然使用這種移位+偏移的方式顯然行不通了。那保護模式如何實現這種 [段:偏移] 的尋址呢?答案就在GDT / LDT 上。

        在保護模式下,CS/DS/SS等雖然仍然被稱爲段寄存器,但是它的函數和實模式下完全不同了,它不再代表真正的段基址,而是一個索引。保存的內容被稱爲選擇子,用於選擇GDT/LDT中的一項。選擇子的結構如下圖所示:

        

        選擇子的高13位爲描述符索引(就是我們的描述符表中的索引值),低兩位爲RPL,全稱Request Privilege Level,請求權限級別??(暫且這麼說吧)用於Ring0~Ring3權限的確認。

        既然段寄存器內容變成了選擇子,那麼如何尋找段基址呢?答案就是上面提到的GDT/LDT,GDT(Global Descriptor Table)全局描述符表(以GDT爲例,LDT類似)GDT表中的元素是全局描述符,它的結構如下圖所示:

        

        它有64位,包含的數據一目瞭然。段基址 1,2 直接組成了32位的地址。段界限 1,2則組成了20位的段界限。DPL兩位爲描述符的權限,與CPL和RPL完成訪問權限的限制,以保護系統或進程。TYPE則表示描述符的類別,包括了存儲段描述符,系統段描述符(TSS,調用門,中斷門,陷阱門,任務門)。

        可能會奇怪,段界限爲20位,那就1M字節啊,這顯然不現實啊,32位的基地址,1M的界限,現在的程序一個模塊都1M多甚至更大了,這怎麼訪問啊!不要着急,這個段界限是有單位的,在描述符中,BYTE6的最高位 G 如果爲0 表示段界限的粒度爲1字節,如果爲1時,表示段界限爲4k,1M個4K,顯然範圍編程了4G了。

        舉例:

        對於數據訪問的保護例子, mov  eax, ds:[ebx]

        這是一個非常平常的一個內存訪問的例子,取內存中的四個字節。如何存取呢?執行的當前的代碼指令位置爲 CS:EIP,CS中保存了代碼段的選擇子內容,CS的最低兩位爲CPL,即當前執行代碼所處的特權級。EIP爲代碼段的偏移。要訪問的內存爲數據段 ( 由DS 指定)中的 EBX 指定偏移處的四個字節。 DS 爲要請求的選擇子,它的低兩位爲 RPL,即請求特權級別,DS中保存的選擇子會指向GDT中的一個描述符,描述符中有一個DPL(如上所述)。對比 CPL ,RPL與DPL的值,滿足DPL>=CPL,DPL >= RPL,且CPL <= RPL。即 當前特權級要高於或等於DS 選擇子規定的特權級別,同時 當前的特權級別,以及DS選擇子規定特權級別都要高於或等於 DS所指向的描述符中定義的特權級別。(注意 DPL,CPL的值 與他們的特權級別 正相反,即CPL值越小,表明當前執行的特權級別越高),過程如下圖所示。

        


        現在問題來了,GDT由誰來創建,系統的每一項描述符如何創建(Windows系統那麼大,有那麼多的段),一個進程也有好多段,它的描述表如何創建,每一個描述符如何生成呢???(留待以後分解)


        不同特權代碼之間的轉移:

        上面的任務完成一部分保護工作,就是內存的訪問保護。同樣,代碼的執行也是要保護的,如何保護?代碼直接的轉移(相互跳轉)。Ring3的權限,無法執行權限級別爲0的代碼。那問題就來了,相互跳轉如何實現,如何實現保護?

        代碼段間的轉移可以由如下的一些指令引起,jmp,call,ret,systementer,sysexit,int n,iret 等。jmp 或 call 可以實現4中轉移:

                1. 目標操作數包含了目標代碼段的段選擇子

                2. 目標操作數指向一個包含目標代碼段選擇子的調用們描述符

                3. 目標操作數指向一個包好目標代碼段選擇子的TSS

                4. 目標操作數指向一個任務門,任務門指向包含目標代碼段選擇子的TSS

        分爲兩類,直接轉移,間接轉移。通過jmp/call直接轉移,對於非一直代碼段,CPL == DPL,RPL <= DPL,也即當前特權級低於目的代碼,選擇子的權限級別等於或高於目標代碼權限級別。對於一直代碼,CPL >= DPL,也即當前的特權級別要低於目標代碼的特權級。

        間接轉移,則是通過門 進行轉移。這個時候有兩個階段的權限對比,間接的轉移,需要訪問門(調用們,中斷門,陷阱們等),因此它首先要有門的權限。其次再是和要轉移到的代碼的特權級別進行比較。

       

        特權級切換:ring0 到 ring3,ring3 到 ring0

        特權級的切換,方法有很多。都需要通過上述的代碼轉移實現。一方面要滿足 跳轉的權限要求,同時還需要保存執行的現場。對於特權的切換,如ring3 到ring0的切換,要保存當前執行的 CS:EIP ,確保執行完ring0,返回後可以繼續執行;ring3 和 ring0 使用不同的棧,因此需要保存當前棧的SS:ESP,以便返回後,將棧切回到跳轉之前的棧狀態。當然還有EFLAGS等值。比較典型的有Windows系統上的內核調用,比如I/O


        分頁存儲:

        80386 CPU支持了分頁機制,分頁機制也節省內存,一個任務不需要將執行的段完全映射到物理內存即可執行。節省內存,分段情況下有大量的大片的內存碎片,實行分頁後,可以將內存碎片降低到4k;同時也爲多進程的實現創造有利條件。

        分頁機制,將內存空間進行劃分,以4K爲單位,可以將整個地址空間劃分爲1M個。每個4K的單位是一頁。爲了將頁表組織起來,可以採用線性結構,那麼1M頁,每頁的首地址爲一項(佔四字節),那麼這個管理結構就需要4M的內存來存儲,顯然浪費。使用兩級的結構,第一級每一項指向一頁,第二級的每一頁中包含了256項。如果將所有的內存都索引到,需要的更多了,4M+4K,貌似更加不合算,其實在使用中,不需要將所有的內存都索引到,只需要將用到的內存創建對應的頁目錄即可,這樣就可以減少頁表佔的內存。如下圖所示,一個地址通過頁目錄表,頁表項表,以及物理頁即可獲得對應地址的物理地址。

        

        在CPU中有控制寄存器,就是CR*,其中的CR0寄存器的 PG位表示了CPU是否開啓了分頁基址。因此在設置號目錄表等項後,還需要將CR0的PG位置位,纔是正式打開了CPU的分頁基址,CPU纔會按照上述的方式去獲取內存內容。

        另外一個寄存器CR3,CR3中保存了頁目錄基地址的物理地址。如上圖所示,CPU在去內存的內容時,首先從CR3中獲取到頁目錄表的物理地址,在根據虛擬地址值去獲取相應的頁目錄項,頁表項,以及對應的物理塊。

        在多進程中,每一個進程都會維護一個頁表,其中保存了進程用到的內存頁的映射。在做進程切換時,要將CR3的值換掉,換成目標進程的CR3值,訪問內存是對應所應的也目錄表也發生了變化。因此對於同一個地址在不同的進程中,會被映射到不同的物理地址上。

        有一個問題,那麼這些也目錄如何進行初始化呢,地址空間如何劃分,相互如何映射呢?誰來負責規劃? 仍然需要探索啊!

        中斷和異常:

        在DOS中(實模式),0~1K 的內存空間是中斷向量表,每個向量由 CS:IP 格式佔用兩個字節(一共256項),直接使用中斷號索引中斷向量的位置,取出CS:IP,即可跳轉到對應的地址去,進行中斷處理。

        類似於指令和內存訪問的CS,DS段寄存器,這裏保存的要跳轉的CS段基址在保護模式下也無法使用了。如何處理?如何尋找中斷處理過程?沒錯,前面說過了通過映射,這也可以通過映射處理。與GDT的表類似,處理中斷和異常,也有一個類似的表,叫做IDT,其中保存了中斷描述符,即中斷門,陷阱門等。它和我們上面說到的調用們類似(結構如下圖),其中包含了 DPL,選擇子以及偏移值。

        

        那麼中斷如何觸發呢?說觸發之前,首先要說一下硬件中斷的映射。在進入保護模式後,0~18 號的中斷號已經被Intel CPU默認的除法錯 等中斷佔據了。因此需要將8259A的中斷信息重新映射到更高一些的中斷號上(具體的映射方法不再介紹了,可以搜索一下資料,有很多)。一般情況下會將硬件中斷 映射到 20H 的地方,因爲0~31號中斷中,19~31是Intel 保留的向量號。32 ~255 的中斷號是留給用戶,因此將硬件中斷重新映射到 20H 開始的中斷號上。

        這樣,只要觸發了中斷,無論 int n 還是硬件中斷,都可以得到一箇中斷號。使用這個中斷號可以索引到IDT中的 門描述符。根據門描述符,使用其中的選擇子,可以獲得中斷處理過程所在段的描述符,從描述符獲取段基址,加上門描述符 中的偏移地址,就得到了中斷處理過程的首地址。

        

        同樣,在整個過程中,不僅要使用到 中斷門等描述符,並且還要訪問GDT 表中的段描述符,這個過程中都要進行CPL,RPL與DPL值的對比,確定有權限進行相應的調用操作,或訪問操作。

        

        保護模式下的I/O:

        保護模式下的I/O有兩個方面的保護:一個是IOPL,類似於DPL,在EFLAGS 寄存器中,第十二,十三位爲IOPL的值,要執行IO指令,要滿足CPL <= IOPL條件纔可以執行,否則執行權限沒效果。

        第二個是IO位圖,I/O位圖開始於TSS中104字節(前104字節爲TSS的固定格式,用於保存任務的狀態),I/O位圖最長可達8K,每一位表示一個字節的端口是否可以訪問。

        因此在進行I/O訪問時,一方面要判斷IOPL的值,同時還要確定要訪問的端口,在I/O位圖中是否可用。

        


        關於保護模式,肯定不止寫的這些內容。但是暫時寫這麼多吧,也就理解了這麼點內容,還是有很多東西沒理解透,以後有了新的理解,在添加新的內容。

        By Andy @ 2015-08-02


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章