3.1認識保護模式
- 先來看一段代碼
定義了三個描述符的GDT,GDT的基址和長度,還有兩個選擇子
- GdtPtr就是放到gdtr裏面的東西,你看他是不是6字節啊!
dd就是4字節,不信你看下面gdtr結構示意圖。
dw就是倆字節
看到沒有,GDT裏面有三個段啊,
,兩個選擇子,定義成了地址!爲啥是那樣定義的呢?這兩個選擇子實際上就是偏移量啊,以後可以從選擇子上面找到描述符啊哈哈哈哈。這裏用的和真是的選擇子不一樣,這裏估計就是爲了仿真把!
- LABEL_DESC_CODE32是代碼段的描述符地址啊
- xor異或
- xor eax,eax,那不就是把eax清零嗎!!
- SHL邏輯左移指令
- shift logical left
- 第三段跳到實模式
- 實現由實模式到保護模式的轉換
- 開頭處註釋中,這個程序被編譯成了COM文件,最簡單的二進制文件。
- 意味着,程序執行時的內存映像和二進制文件映像是一樣的
- 上面的程序中定義的 secton並沒有什麼實質上作用,
- 不定義它們,從執行結果來看也是一樣的(編譯出來的二進制有微小差別)
- 定義它們只是使代碼結構上清晰,且後面我們對這個程序漸漸擴展的時候,它還有一點妙用
- [SECTION.gdt]這個段
- Descriptor是在 pm.inc中定義的宏:
DB定義字節類型變量,一個字節數據佔1個字節單元,讀完一個,偏移量加1
DW定義字類型變量,一個字數據佔2個字節單元,讀完一個,偏移量加2
DD定義雙字類型變量,一個雙字數據佔4個字節單元,讀完一個,偏移量加4
上面的圖片是一個宏,這特媽不就是段描述符嗎。8個字節啊!!
-
這個宏表示的不是一段代碼,而是一個數據結構,大小8字節。
-
並列有3個 Descriptor,看上去是個結構數組,這個數組的名字叫做GDT。
數組的名字叫GDT,從何體現?
- GdtLen是GDT長度,
- GdtPtr也是個小的數據結構,
- 前2字節是GDT的長度,後4字節是GDT的基地址
-
到一個代碼段
-
[BITS 16]指明它是一個16位代碼段
-
這段程序修改一些GDT中的值,
- 然後執行一些不常見的指令,
- 最後通過jmp實現跳轉
-
這一句將“真正進入保護模式”。
- 實際它將跳轉到第三個section,即[ SECTIONS32]中,
- 這個段是32位的,執行最後一小段代碼。
- 這段代碼看上去是往某個地址處寫入了2字節,然後就無限循環。
- 首先按照註釋中所說的辦法編譯:
- 然後在 Virtual PC中按第2章2.5節的方法將包含 pmtestl. com的文件夾共享,且運行它,結果如圖3-1
- 打印了一個紅色的字符P,然後再也不動了。
- 程序的最後一部分代碼中寫入的2字節是寫進顯存
- 什麼是GDT?
- 那些怪怪的指令到底在做什麼?
- 瞭解到
- 程序定義了一個叫做GDT的數據結構。
- 16位代碼進行了一些與GDT有關的操作。
- 程序最後跳到32位代碼中做了一點操作顯存的工作
- 不明就裏的內容
- GDT是什麼?它是幹什麼用的?
- 程序對GDT做了什麼?
- jmp Selectorcode32:0跟我們從前用過的jmp有什麼不同?
- 不打算全面介紹保護模式的課程,本着夠用原則,不涉及保護模式的所有內容,只要能自由地編寫操作系統代碼就足夠
- V86是保護模式的一部分,如果你不想在自己的操作系統中支持16位程序,你可能永遠都不需要知道它的實現方法,學習它簡直是浪費
3.1.1 Global Descriptor Table
- IA-32下,CPU有兩種工作模式:實模式和保護模式。
- 打開自己的PC,開始時CPU工作在實模式下,經過某種機制之後,オ進入保護模式。
- 保護模式下,CPU有着巨大的尋址能力,併爲強大的32位操作系統提供了更好的硬件保障。
- Intel8086是16位的CPU,有16位的寄存器、16位的數據總線
- 及20位的地址總線和1 MB尋址能力。
- 地址是由段和偏移兩部分組成的,物理地址遵循這樣的計算公式
物理地址=段值( Segment)×16+偏移( offset)
段值和偏移都是16位的
- 80386開始, Intel家族的CPU進入32位時代。
- 80386有32位地址線,尋址空間達4GB。
- 單從尋址這方面說,使用16位寄存器的方法已經不夠用了。
- 這時候,我們需要新的方法來提供更大的尋址能力。
- 當然,保護模式的優點不僅僅在這一個方面。
- 實模式下,16位寄存器需用“段:偏移”才能達到IMB的尋址能力
- 有了32位寄存器,一個寄存器就可以尋址4GB的空間,是不是從此段值就被拋棄?
- 沒有,地址仍用“SEG:OFFSET”表示,不過保護模式下“段”的概念發生變化
- 實模式下,段值還可看做地址一部分,段值爲XXXXH表示以 XXXXOH開始的一段內存
- 保護模式下,段值仍由原來16位的cs、ds等寄存器表示,但此時它僅僅變成一個索引,指向GDT的一個表項,表項定義了段的起始地址、界限、屬性等
- 就是GDT(還可能是LDT,這個以後再介紹)。
- GDT表項叫做描述符( Descriptor)。
-
GDT提供段式存儲機制,這種機制是通過段寄存器和GDT中的描述符共同提供。
-
看一下如圖3-2所示的描述符的結構
-
示意圖表示的是代碼段和數據段描述符,還有系統段描述符和門描述符。
-
BYTE5和BTYE6中的一堆屬性看上覆雜
-
段的基址和界限。
- 由於歷史問題,它們都被拆開
-
那些屬性,暫時先不管
- Descriptor這個宏用比較自動化的方法把段基址、段界限和段屬性安排在一個描述符中合適的位置,有興趣的讀者可以研究這個宏的具體內容
- 本例GDT中有3個描述符,DESC_DUMMY、 DESC_CODE32和 DESC_ VIDEO
- DESC_VIDEO,段基址是OB8000h。
- 指向的正是顯存
- GDT中的每一個描述符定義一個段,
- cS、ds等段寄存器是如何和這些段對應起來的呢?
- 段[SECTON.s32]中有兩句代碼是這樣
- 段寄存器gs值變成了 Selector Video,
- Selector Video是這樣定義
的意思是count=$ -offset A。
- 它好像是DESC_ VIDEO這個描述符相對於GDT基址的偏移。
- 有一個專門的名稱,叫做選擇子( Selector),它也不是一個偏移,而是稍稍複雜一些,它的結構如圖3-3
- T和RPL都爲零時,選擇子就變成了對應描述符相對於GDT基址的
偏移,就好像我們程序中那樣。 - mov [gs:edi], ax
- gs的值是 Selector Video,它指示對應顯存的描述符DESC VIDEO
- 這條指令將把ax的值寫入顯存中偏移位edi的位置
- 整個的尋址方式可以用如圖3-4所示
- 示意圖,真實的描述符中段基址以及段偏移等內容在描述符中的位置不是像圖中這樣安排
- “段:偏移”形式的邏輯地址經段機制轉化成“線性地址”,而不是“物理地址”,原因以後會提到。
- 上面程序中,線性地址就是物理地址
- 包含描述符的,不僅可是GDT,也可LDT。
-
只剩下[ SECTION.s16]這一段沒有分析。
-
既然[ SECTION.s32]是32位的程序,並且在保護模式下執行,
-
[SECTION.s16]的任務一定是從實模式向保護模式跳轉
3.1.2實模式到保護模式,不一般的jmp
- 到 SECTION.s16]看一下初始化32位代碼段描述符的這一段,
- 先將LABEL_SEG_CODE32的物理地址(即[SECTION.s32]這個段的物理地址)賦給eax
- 然後把它分成三部分賦給描述符DESC_CODE32中的相應位置
- DESC_CODE32的段界限和屬性已經指定,所以至此,DESC_CODE32的初始化全部完成
- 接下來把GDT的物理地址填充到了 GdtPtr這個6字節的數據結構,
- 然後執行
lgdt [GdtPtr]
- 這一句將GdtPtr指示的6字節加載到gdtr,gdtr的結構如圖3-5
- GdtPtr和gdr的結構完全一樣
- 下面是關中斷,之所以關中斷,
- 因爲保護模式下中斷處理的機制是不同的,不關掉中斷將會出現錯誤
- 再下面幾句打開A20地址線。
- 8086是用SEG:OFFSET這樣的模式分段的,
- 它能表示的最大內存是FFFF:FFFF即10FFEFh
- FFFF0+0FFFF=10FFEFh
- 1MB=FFFFF是最大地址啦!!
- 8086只有20位地址總線,只能尋址1MB,如果試圖訪問超過1MB的地址時會怎樣?
- 系統並不異常,回捲(wrap)回去,重新從地址零尋址。
- 到了80286時,真的可以訪問到1MB以上的內存,遇到同樣的情況,系統不會再回卷尋址,造成了向上不兼容,爲保證百分百兼容
- IBM用8042鍵盤控制器來控制第20個(從零開始數)地址位,這就是A20地址線,如果不被打開,第20個地址位將會總是零。
- 爲訪問所有內存,需把A20打開,開機默認關閉
- 如何打開呢?有點複雜,只用通過操作端口92h來實現這一種方式,
- 如代碼3-1中那樣
- 這不是惟一的方法,且在某些個別情況下,這種方法可能會出現問題。但在絕大多數情況下,它是適用的。
- 把寄存器crO的第0位置爲1。
- 這一位正是決定實模式和保護模式的關鍵,cr0的結構如圖3-6