1.概述
1.實模式
實模式又稱實地址模式,實,即真實,這個真實分為兩個方面,一個方面是運行真實的指令,對指令的動作不作區分,直接執行指令的真實功能,另一方面是發往內存的地址是真實的,對任何地址不加限制地發往內存。
指令的操作數,可以是寄存器、內存地址、常數,其實通常情況下是寄存器,AX、CX 就是 x86 CPU 中的寄存器。
下面我們就去看看 x86? CPU 在實模式下的寄存器。表中每個寄存器都是 16 位的。
特別注意段寄存器(CS,DS,ES,SS)分別指代碼段寄存器,數據段寄存器,輔助段寄存器,堆棧段寄存器
雖然有了寄存器,但是數據和指令都是存放在內存中的。通常情況下,需要把數據裝載進寄存器中才能操作,還要有獲取指令的動作,這些都要訪問內存才行,而我們知道訪問內存靠的是地址值。
上面就是所謂的分段內存管理模型下的取指和內存訪問的方法,所有的內存地址都是由段寄存器左移 4 位,再加上一個通用寄存器中的值或者常數形成地址,然后由這個地址去訪問內存。
其中代碼段是由CS和IP確定的, 而堆棧是由SS和SP段確定
data SEGMENT ;定義一個數據段存放Hello World!hello DB 'Hello World!$' ;注意要以$結束
data ENDS
code SEGMENT ;定義一個代碼段存放程序指令ASSUME CS:CODE,DS:DATA ;告訴匯編程序,DS指向數據段,CS指向代碼段
start:MOV AX,data ;將data段首地址賦值給AX MOV DS,AX ;將AX賦值給DS,使DS指向data段LEA DX,hello ;使DX指向hello首地址MOV AH,09h ;給AH設置參數09H,AH是AX高8位,AL是AX低8位,其它類似INT 21h ;執行DOS中斷輸出DS指向的DX指向的字符串helloMOV AX,4C00h ;給AX設置參數4C00hINT 21h ;調用4C00h號功能,結束程序
code ENDS
END start
?LEA 是取地址指令,MOV 是數據傳輸指令,就是 INT 中斷你可能還不太明白,下面我們就來研究它。
中斷即中止執行當前程序,轉而跳轉到另一個特定的地址上,去運行特定的代碼。在實模式下它的實現過程是先保存 CS 和 IP 寄存器,然后裝載新的 CS 和 IP 寄存器,那么中斷是如何產生的呢?
第一種情況是,中斷控制器給 CPU 發送了一個電子信號,CPU 會對這個信號作出應答。隨后中斷控制器會將中斷號發送給 CPU,這是硬件中斷。第二種情況就是 CPU 執行了 INT 指令,這個指令后面會跟隨一個常數,這個常數即是軟中斷號。這種情況是軟件中斷。
為了實現中斷,就需要在內存中放一個中斷向量表,這個表的地址和長度由 CPU 的特定寄存器 IDTR 指向。實模式下,表中的一個條目由代碼段地址和段內偏移組成,如下圖所示。
有了中斷號以后,CPU 就能根據 IDTR 寄存器中的信息,計算出中斷向量中的條目,進而裝載 CS(裝入代碼段基地址)、IP(裝入代碼段內偏移)寄存器,最終響應中斷。
2.保護模式
保護模式相比于實模式,增加了一些控制寄存器和段寄存器,擴展通用寄存器的位寬,所有的通用寄存器都是 32 位的,還可以單獨使用低 16 位,這個低 16 位又可以拆分成兩個 8 位寄存器,如下表。
為了區分哪些指令(如 in、out、cli)和哪些資源(如寄存器、I/O 端口、內存地址)可以被訪問,CPU 實現了特權級。特權級分為 4 級,R0~R3,每個特權級執行指令的數量不同,R0 可以執行所有指令,R1、R2、R3 依次遞減,它們只能執行上一級指令數量的子集。而內存的訪問則是靠后面所說的段描述符和特權級相互配合去實現的。如下圖.
目前為止,內存還是分段模型,要對內存進行保護,就可以轉換成對段的保護。
由于 CPU 的擴展導致了 32 位的段基地址和段內偏移,還有一些其它信息,所以 16 位的段寄存器肯定放不下。放不下就要找內存借空間,然后把描述一個段的信息封裝成特定格式的段描述符,放在內存中,其格式如下。
一個段描述符有 64 位 8 字節數據,里面包含了段基地址、段長度、段權限、段類型(可以是系統段、代碼段、數據段)、段是否可讀寫,可執行等。雖然數據分布有點亂,這是由于歷史原因造成的。
多個段描述符在內存中形成全局段描述符表,該表的基地址和長度由 CPU 的 GDTR 寄存器指示。如下圖所示。
段寄存器中不再存放段基地址,而是具體段描述符的索引,訪問一個內存地址時,段寄存器中的索引首先會結合 GDTR 寄存器找到內存中的段描述符,再根據其中的段信息判斷能不能訪問成功。
如果你認為 CS、DS、ES、SS、FS、GS 這些段寄存器,里面存放的就是一個內存段的描述符索引,那你可就草率了,其實它們是由影子寄存器、段描述符索引、描述符表索引、權限級別組成的。如下圖所示
影子寄存器是靠硬件來操作的,對系統程序員不可見,是硬件為了減少性能損耗而設計的一個段描述符的高速緩存,不然每次內存訪問都要去內存中查表,那性能損失是巨大的,影子寄存器也正好是 64 位,里面存放了 8 字節段描述符數據。
通常情況下,CS 和 SS 中 RPL 就組成了 CPL(當前權限級別),所以常常是 RPL=CPL,進而 CPL 就表示發起訪問者要以什么權限去訪問目標段,當 CPL 大于目標段 DPL 時,則 CPU 禁止訪問,只有 CPL 小于等于目標段 DPL 時才能訪問。
3.保護模式中斷
實模式下 CPU 不需要做權限檢查,所以它可以直接通過中斷向量表中的值裝載 CS:IP 寄存器就好了。
而保護模式下的中斷要權限檢查,還有特權級的切換,所以就需要擴展中斷向量表的信息,即每個中斷用一個中斷門描述符來表示,也可以簡稱為中斷門,中斷門描述符依然有自己的格式,如下圖所示。
同樣的,保護模式要實現中斷,也必須在內存中有一個中斷向量表,同樣是由 IDTR 寄存器指向,只不過中斷向量表中的條目變成了中斷門描述符,如下圖所示
產生中斷后,CPU 首先會檢查中斷號是否大于最后一個中斷門描述符,x86 CPU 最大支持 256 個中斷源(即中斷號:0~255),然后檢查描述符類型(是否是中斷門或者陷阱門)、是否為系統描述符,是不是存在于內存中。
接著,檢查中斷門描述符中的段選擇子指向的段描述符。
保護模式下中斷過程: 對于保護模式下的中斷,如果中斷號在0-255。首先通過IDTR和中斷號找到中斷門描述符,根據中斷門描述符屬性部分中的DPL來判斷當前權限是否能執行該中斷。如果CPL<=DPL,則允許通過段選擇子和段偏移訪問對應的段。 接著根據段選擇子可以在GDTR或LDTR中找到對應段的描述符。訪問段首先需要檢查段選擇子中的DPL,如果CPL=DPL則可以直接訪問;如果CPL>DPL則需要切換權限才能進一步訪問,例如從用戶態切換到內核態,此時會從TSS中加載具體權限的相應棧寄存器,切換棧運行環境后才可以繼續訪問。所謂訪問段就是將對應段的段選擇子和段內偏移加載到對應寄存器(CS、EIP),然后按序取址執行即可。執行完后會恢復到原來的權限狀態。 上述涉及到保護模式下的權限問題,什么時候能跨權限執行? ① 訪問高權限的一致代碼段(例如共享的,不訪問保護資源的代碼);② 通過合法的權限切換手段(例如中斷、異常、系統調用等)
4.切換到保護模式
x86 CPU 在第一次加電和每次 reset 后,都會自動進入實模式,要想進入保護模式,就需要程序員寫代碼實現從實模式切換到保護模式。切換到保護模式的步驟如下。
第一步,準備全局段描述符表,代碼如下。
GDT_START:
knull_dsc: dq 0
kcode_dsc: dq 0x00cf9e000000ffff
kdata_dsc: dq 0x00cf92000000ffff
GDT_END:
GDT_PTR:
GDTLEN dw GDT_END-GDT_START-1
GDTBASE dd GDT_START
第二步,加載設置 GDTR 寄存器,使之指向全局段描述符表。
lgdt [GDT_PTR]
第三步,設置 CR0 寄存器,開啟保護模式。
;開啟 PE
mov eax, cr0
bts eax, 0 ; CR0.PE =1
mov cr0, eax
第四步,進行長跳轉,加載 CS 段寄存器,即段選擇子。因為在剛進入保護模式下, 原來的段寄存器信息還殘留在,而這些數據在保護模式下是沒有用的, 所以需要長跳轉來清空指令流水線,使CPU重新加載信息
jmp dword 0x8 :_32bits_mode ;_32bits_mode為32位代碼標號即段偏移
接下來,CPU 發現了 CRO 寄存器第 0 位的值是 1,就會按 GDTR 的指示找到全局描述符表,然后根據索引值 8,把新的段描述符信息加載到 CS 影子寄存器,當然這里的前提是進行一系列合法的檢查。到此為止,CPU 真正進入了保護模式,CPU 也有了 32 位的處理能力。
5.長模式
長模式又名 AMD64,因為這個標準是 AMD 公司最早定義的,它使 CPU 在現有的基礎上有了 64 位的處理能力,既能完成 64 位的數據運算,也能尋址 64 位的地址空間
長模式相比于保護模式,增加了一些通用寄存器,并擴展通用寄存器的位寬,所有的通用寄存器都是 64 位,還可以單獨使用低 32 位。這個低 32 位可以拆分成一個低 16 位寄存器,低 16 位又可以拆分成兩個 8 位寄存器,如下表。
長模式依然具備保護模式絕大多數特性,如特權級和權限檢查。相同的部分就不再重述了,這里只會說明長模式和保護模式下的差異。下面我們來看看長模式下段描述的格式,如下圖所示。
在長模式下,CPU 不再對段基址和段長度進行檢查,只對 DPL 進行相關的檢查,這個檢查流程和保護模式下一樣。
在長模式(64 位模式)中,x86 架構對段機制進行了根本性簡化:
- 段基地址:所有段寄存器(CS、DS、ES、SS 等)的基地址固定為 0。
- 段界限:所有段的界限固定為最大值(0xFFFFFFFFFFFFFFFF)。
- 段描述符:仍然存在,但僅用于權限檢查(如 CPL、DPL)和內存類型標識(如代碼段 / 數據段)。
這種設計使得段機制不再參與地址計算,而是由RIP 寄存器直接提供線性地址,就像平坦模式一樣。
在長模式下,RIP 寄存器的值(或內存操作數的有效地址)直接作為 MMU 的輸入虛擬地址
下面我們來寫一個長模式下的段描述符表,加深一下理解,如下所示.
ex64_GDT:
null_dsc: dq 0
;第一個段描述符CPU硬件規定必須為0
c64_dsc:dq 0x0020980000000000 ;64位代碼段
;無效位填0
;D/B=0,L=1,AVL=0
;P=1,DPL=0,S=1
;T=1,C=0,R=0,A=0
d64_dsc:dq 0x0000920000000000 ;64位數據段
;無效位填0
;P=1,DPL=0,S=1
;T=0,C/E=0,R/W=1,A=0
eGdtLen equ $ - null_dsc ;GDT長度
eGdtPtr:dw eGdtLen - 1 ;GDT界限dq ex64_GDT
6.長模式中斷
保護模式下為了實現對中斷進行權限檢查,實現了中斷門描述符,在中斷門描述符中存放了對應的段選擇子和其段內偏移,還有 DPL 權限,如果權限檢查通過,則用對應的段選擇子和其段內偏移裝載 CS:EIP 寄存器。如果你還記得中斷門描述符,就會發現其中的段內偏移只有 32 位,但是長模式支持 64 位內存尋址,所以要對中斷門描述符進行修改和擴展,下面我們就來看看長模式下的中斷門描述符的格式,如下圖所示。
首先為了支持 64 位尋址中斷門描述符在原有基礎上增加 8 字節,用于存放目標段偏移的高 32 位值。其次,目標代碼段選擇子對應的代碼段描述符必須是 64 位的代碼段。最后其中的 IST 是 64 位 TSS 中的 IST 指針,因為我們不使用這個特性,所以不作詳細介紹。
7.切換到長模式
我們既可以從實模式直接切換到長模式,也可以從保護模式切換長模式。切換到長模式的步驟如下。第一步,準備長模式全局段描述符表。
ex64_GDT:
null_dsc: dq 0
;第一個段描述符CPU硬件規定必須為0
c64_dsc:dq 0x0020980000000000 ;64位代碼段
d64_dsc:dq 0x0000920000000000 ;64位數據段
eGdtLen equ $ - null_dsc ;GDT長度
eGdtPtr:dw eGdtLen - 1 ;GDT界限dq ex64_GDT
第二步,準備長模式下的 MMU 頁表,這個是為了開啟分頁模式,切換到長模式必須要開啟分頁,想想看,長模式下已經不對段基址和段長度進行檢查了,那么內存地址空間就得不到保護了。而長模式下內存地址空間的保護交給了 MMU,MMU 依賴頁表對地址進行轉換,頁表有特定的格式存放在內存中,其地址由 CPU 的 CR3 寄存器指向。
mov eax, cr4
bts eax, 5 ;CR4.PAE = 1
mov cr4, eax ;開啟 PAE
mov eax, PAGE_TLB_BADR ;頁表物理地址
mov cr3, eax
第三步,加載 GDTR 寄存器,使之指向全局段描述表:
lgdt [eGdtPtr]
第四步,開啟長模式,要同時開啟保護模式和分頁模式,在實現長模式時定義了 MSR 寄存器,需要用專用的指令 rdmsr、wrmsr 進行讀寫,IA32_EFER 寄存器的地址為 0xC0000080,它的第 8 位決定了是否開啟長模式,MSR是CPU的一組64位寄存器,可以分別通過RDMSR和WRMSR 兩條指令進行讀和寫的操作,前提要在ECX中寫入MSR的地址。 對于RDMSR 指令,將會返回相應的MSR 中64bit 信息到(EDX:EAX)寄存器中;對于WRMSR 指令,把要寫入的信息存入(EDX:EAX)中,執行寫指令后,即可將相應的信息存入ECX 指定的MSR 中。MSR 的指令必須執行在level 0 或實模式下。
;開啟 64位長模式
mov ecx, IA32_EFER
rdmsr
bts eax, 8 ;IA32_EFER.LME =1
wrmsr
;開啟 保護模式和分頁模式
mov eax, cr0
bts eax, 0 ;CR0.PE =1
bts eax, 31
mov cr0, eax
第五步,進行跳轉,加載 CS 段寄存器,刷新其影子寄存器。
jmp 08:entry64 ;entry64為程序標號即64位偏移地址
切換到長模式和切換保護模式的流程差不多,只是需要準備的段描述符有所區別,還有就是要注意同時開啟保護模式和分頁模式。原因在上面已經說明了。
參考:LMOS 操作系統