基本概念
保護模式下的實模式
保護模式:定址採用32位段和偏移量,最大
定址空間4GB,最大分段4GB (Pentium Pre及以後為64GB)。在保護模式下CPU可以進入虛擬8086方式,這是在保護模式下的實模式程式運行環境。
運行命令
程式運行的實質是什麼?其實很簡單,就是指令的執行,顯然CPU是指令得以執行的硬體保障,那么CPU如何知道指令在什麼地方呢?
80x86系列是使用CS
暫存器配合IP暫存器來通知CPU指令在記憶體中的位置.
程式指令在執行過程中一般還需要有各種數據,80x86系列有DS、ES、FS、GS、SS等用於指示不同用途的數據段在記憶體中的位置。
程式可能需要調用系統的服務子程式,80x86系列使用中斷機制來實現
系統服務。
總的來說,這些就是實模式下一個程式運行所需的主要內容(其它如跳轉、返回、連線埠操作等相對來說比較次要。)
保護模式
保護模式---從程式運行說起
無論實模式還是保護模式,根本的問題還是程式如何在其中運行。
因此我們在學習保護模式時應該時刻圍繞這個問題來思考。和實模式下一樣,保護模式下程式運行的實質仍是“CPU執行指令,操作相關數據”,因此實模式下的各種
代碼段、
數據段、
堆疊段、
中斷服務程式仍然存在,且功能、作用不變。
那么保護模式下最大的變化是什麼呢?答案可能因人而異,我的答案是“地址轉換方式”變化最大。
方式比較
先看一下實模式下的地址轉換方式,假設我們在ES中存入0x1000,DI中存入0xFFFF,那么ES:DI=0x1000*0x10+0xFFFF=0x1FFFF,這就是眾所周知的“左移4位加偏移”。
那么如果在保護模式下呢?假設上面的數據不變ES=0x1000,DI=0xFFFF,現在ES:DI等於什麼呢?
公式如下:(註:0x1000=1000000000000b= 10 0000 0000 0 00)ES:DI=全局描述符表中第0x200項描述符給出的段基址+0xFFFF
現在比較一下,好象是不一樣。再仔細看看,又好象沒什麼區別!
為什麼說沒什麼區別,因為我的想法是,既然ES中的內容都不是真正的段地址,憑什麼實模式下稱ES為“段暫存器”,而到了保護模式就說是“選擇子”?你覺得呢?
其實它們都是一種映射,只是映射規則不同而已:在實模式下這個“地址轉換方式”是“左移4位”;在保護模式下是“查全局/局部描述表”。前者是系統定義的映射方式,後者是用戶自定義的轉換方式。而它影響的都是“shadow register”從函式的觀點來看,前者是表達式函式,後者是列舉式函式:
實模式: F(es-->segment)={segment |segment=es*0x10}
保護模式:F(es-->segment)={segment |(es,segment)∈GDT/LDT}
其中GDT、LDT分別表示全局描述符表和局部描述符表。
組成部分
保護模式最基本的組成部分是圍繞著“地址轉換方式”的變化增設了相應的機構。
1、數據段
前面說過,實模式下的各種代碼段、數據段、堆疊段、中斷服務程式仍然存在,我將它們統稱為“數據段”,本文從此向下凡提到數據段都是使用這個定義。
2、描述符
保護模式下引入描述符來描述各種數據段,所有的描述符均為8個位元組(0-7),由第5個位元組說明描述符的類型,類型不同,描述符的結構也有所不同。
若干個描述符集中在一起組成描述符表,而描述符表本身也是一種數據段,也使用描述符進行描述。從現在起,“地址轉換”由描述符表來完成,從這個意義上說,描述符表是一張地址轉換函式表。
3、選擇子
選擇子是一個2位元組的數,共16位,最低2位表示RPL,第3位表示查表是利用GDT(全局描述符表)還是LDT(局部描述符表)進行,最高13位給出了所需的描述符在描述符表中的地址。(註:13位正好足夠定址8K項)有了以上三個概念之後可以進一步工作了,現在程式的運行與實模式下完全一樣!!!各段暫存器仍然給出一個“段值”,只是這個“假段值”到真正的段地址的轉換不再是“左移4位”,而是利用描述符表來完成。但現在出現一個新的問題是:
系統如何知道GDT/LDT在記憶體中的位置呢?
為了解決這個問題,顯然需要引入新的暫存器用於指示GDT/LDT在記憶體中的位置。在80x86系列中引入了兩個新暫存器GDTR和LDTR,其中GDTR用於表示GDT在記憶體中的段地址和段限(就是表的大小),因此GDTR是一個48位的暫存器,其中32位表示段地址,16位表示段限(最大64K,每個描述符8位元組,故最多有64K/8=8K個描述符)。LDTR用於表示LDT在記憶體中的位置,但是因為LDT本身也是一種數據段,它必須有一個描述符,且該描述符必須放在GDT中,因此LDTR使用了與DS、ES、CS等相同的機制,其中只存放一個“選擇子”,通過查GDT表獲得LDT的真正
記憶體地址。對了,還有中斷要考慮,在80x86系列中為中斷服務提供中斷/陷阱描述符,這些描述符構成中斷描述符表(IDT),並引入一個48位的全地址暫存器存放IDT的記憶體地址。理論上IDT表同樣可以有8K項,可是因為80x86隻支持256箇中斷,因此IDT實際上最大只能有256項(2K大小)。
問題總結
基本問題
前面介紹了保護模式的基本問題,也是核心問題,解決了上面的問題,程式就可以在保護模式下運行了。
但眾所周知80286以後在保護模式下實現了對多任務的硬體支持。我的第一反應是:為什麼不在實模式下支持多任務,是不能還是不願?
思考之後,我的答案是:實模式下能實現多任務。
因為多任務的關鍵是有了描述符,可以給出關於數據段的額外描述,如許可權等,進而在這些附加信息的基礎上進行相應的控制,而實模式下缺乏描述符,但假設我們規定各段的前2個位元組或若干位元組用於描述段的附加屬性,我覺得和使用描述符這樣的機制沒有本質區別,如果再附加其他機制...基於上述考慮,我更傾向於認為任務是獨立於保護模式之外的功能。
下面我們來分析一下任務。任務的實質是什麼呢?很簡單,就是程式嘛!!
所謂任務的切換其實就是程式的切換!!
現在問題明朗了。實模式下程式一個接一個運行,因此程式運行的“環境”不必保存;保護模式下可能一個程式在運行過程中被暫停,轉而執行下一個程式,我們要做什麼?很容易想到保存程式運行的環境就行了(想想遊戲程式的保存進度功能),比如各暫存器的值等。
顯然這些“環境”數據構成了一類新的數據段(即TSS)。延用前面的思路,
給這類數據段設定描述符(TSS描述符),將該類描述符放在GDT中(不能放在LDT中,因為80x86不允許:)),最後再加一個TR暫存器用於查表。TR是一個起“選擇子”作用的暫存器,16位。
好了,任務切換的基本工作就是將原任務的“環境”存入TSS數據段,更新TR暫存器,系統將自動查GDT表獲得並裝載新任務的“環境”,然後轉到新任務執行。
附加要求
為什麼叫附加要求,因為現在任務還不能很好地工作。前面說過,任務實質上是程式,不同的程式是由不同的用戶寫的,所有這些程式完全可能使用相同的地址空間,而任務的切換過程一般不會包括記憶體數據的刷新,不是不可能,而是如果那樣做太浪費了。因此必須引入分頁機制才可能有效地完成對多任務的支持。
分頁的實質就是實現程式內地址到物理地址的映射,這也是一個“地址轉換”機制,同樣可以使用前面的方案(即類似GDT的做法):首先建立頁表這樣一種數據段,在80x86中使用二級頁表方案,增設一個CR3暫存器用於存放一級頁表(又稱為頁目錄)在記憶體中的地址,CR3共32位,其低12位總是為零,高20位指示頁目錄的記憶體地址,因此頁目錄總是按頁對齊的。CR3作為任務“環境”的一部分在任務切換時被存入TSS數據段中。
當然還得有相應的缺頁中斷機制及其相關暫存器CR2(頁故障線性地址暫存器)。
保護模式下增加了什麼?
1、暫存器 GDR LDR IDR TR CR3
2、數據段 描述符表(GDT LDT) 任務數據段(TSS) 頁表(頁目錄 二級頁表)
3、機制 許可權檢測(利用選擇子/描述符/頁表項的屬性位)
線性地址到物理地址的映射
常用名詞
前面內容中出現過的不再解釋。
1、RPL 選擇子當中的許可權位確定的許可權
2、CPL 特指CS中的選擇子當中的許可權位確定的許可權
3、EPL EPL=Max(RPL,CPL),即RPL和CPL中數值較大的,或說許可權等級較小的
4、DPL 描述符中的許可權位確定的許可權
5、PL 泛指以上4種特權級
6、任務特權 =CPL
7、I/O特權 由EFLAGS暫存器的位13、14確定的許可權
8、一致代碼段 一種特殊的代碼段,它在CPL>=DPL時允許訪問
正常的代碼段在CPL=DPL RPL<=DPL時才允許訪問