保護模式(Protect Mode) (1)
到底什麼是保護模式?誰又在保護誰?是通過什麼方式達到保護目的的? 爲什麼要保護呢?帶着這些疑問和幾分好奇,我們開始了第三章的學習,開始隨着作者一步一步地體會“保護”二字的內在含義。那個連胚胎都還不是的“操作系統” 究竟能否正常發育,這一章的“保護”很關鍵!
3.1 認識保護模式
在好奇心的驅使下,人總是很勤勞的,作者如是說。
接着作者馬上就給我們潑來了一頭霧水:
;==========================================
; pmtest1.asm
; 編譯方法:nasm pmtest1.asm -o pmtest1.bin
;==========================================
%include "pm.inc" ; 常量, 宏, 以及一些說明
org 07c00h
jmp LABEL_BEGIN
[SECTION .gdt]
; GDT
; 段基址, 段界限 , 屬性
LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符
LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + DA_32 ; 非一致代碼段
LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW ; 顯存首地址
; GDT 結束
GdtLen equ $ - LABEL_GDT ; GDT 長度
GdtPtr dw GdtLen - 1 ; GDT 界限
dd 0 ; GDT 基地址
; GDT 選擇子
SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT
SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT
; END of [SECTION .gdt]
[SECTION .s16]
[BITS 16]
L ABEL_BEGIN:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0100h
; 初始化 32 位代碼段描述符
xor eax, eax
mov ax, cs
shl eax, 4
add eax, LABEL_SEG_CODE32
mov word [LABEL_DESC_CODE32 + 2], ax
shr eax, 16
mov byte [LABEL_DESC_CODE32 + 4], al
mov byte [LABEL_DESC_CODE32 + 7], ah
; 爲加載 GDTR 作準備
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_GDT ; eax <- gdt 基地址
mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址
; 加載 GDTR
lgdt [GdtPtr]
; 關中斷
cli
; 打開地址線A20
in al, 92h
or al, 00000010b
out 92h, al
; 準備切換到保護模式
mov eax, cr0
or eax, 1
mov cr0, eax
; 真正進入保護模式
jmp dword SelectorCode32:0 ; 執行這一句會把 SelectorCode32 裝入 cs,
; 並跳轉到 Code32Selector:0 處
; END of [SECTION .s16]
[SECTION .s32]; 32 位代碼段. 由實模式跳入.
[BITS 32]
LABEL_SEG_CODE32:
mov ax, SelectorVideo
mov gs, ax ; 視頻段選擇子( 目的)
mov edi, (80 * 11 + 79) * 2 ; 屏幕第 11 行, 第 79 列。
mov ah, 0Ch ; 0000: 黑底 1100: 紅字
mov al, 'P'
mov [gs:edi], ax
; 到此停止
jmp $
SegCode32Len equ $ - LABEL_SEG_CODE32
; END of [SECTION .s32]
---------------------------------------%include "pm.inc"-----------------------------------------------
;
; 描述符
; usage: Descriptor Base, Limit, Attr
; Base: dd
; Limit: dd (low 20 bits available)
; Attr: dw (lower 4 bits of higher byte are always 0)
%macro Descriptor 3
dw %2 & 0FFFFh ; 段界限1
dw %1 & 0FFFFh ; 段基址1
db (%1 >> 16) & 0FFh ; 段基址2
dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) ; 屬性1 + 段界限2 + 屬性2
db (%1 >> 24) & 0FFh ; 段基址3
%endmacro ; 共 8 字節
如果你是第一次讀到這裏,不知道你對這段代碼有怎麼的感覺,可能對開頭的耐心到第二頁就消失的差不多了,然後很快的掃視,很短時間之後,你已經到達了代碼末尾。
知我者作者也!我確確實實就是這樣的感受。我只記住了一句話:這段代碼實現了從實模式到保護模式的轉換!
我們看到,整個程序有很多個類似[SECTION .xxx] 的東西,這個是用來把程序大卸八塊的;我們還看到,[SECTION .gdt] 後面有3 個帶 Descriptor 字樣的行。
先來看看這個Descriptor 是什麼東東,看字面意思,我們知道它好像描述了一些什麼東西……
是的,它是一個宏,而且還帶了3 個參數,不過它代表的不是一段可執行的代碼,而是一個數據結構!我們暫且把它看成是結構體好了,彙編器處理的時候會直接把這個結構體替換進來。我們知道,一個dw 兩個字節,一個db 一個字節,總共有3 個dw 和2 個db ,所以這個結構體總共有3*2+2*1 = 8 個字節,跟註釋說的是一致的。
其中,%1 表示第一個參數,%2 代表第二個參數,%3 代表第三個參數。我們又看到,這些個參數被支離破碎地填充到了Descriptor 的8 個字節裏面。
怎麼搞這麼麻煩啊! ?你問我,呵呵,我也不知道,反正這個叫GDT 的結構體有點變態 是不是,更可惡的是還不止一個。
相比GDT 這個變態的傢伙,緊挨着它屁股後面的這個數據結構GdtPtr 就要單純多了。
GdtPtr dw GdtLen - 1 ; GDT 界限
dd 0 ; GDT 基地址
還真是GDT 的跟屁蟲!你看到了的:裏面的東西全都是跟GDT 有關的。 頭2 個字節描述了GDT 的身高--- 界限;後4 個字節想要透露出GDT 的藏身地--- 基地址。( 其實剛開始GdtPtr 自己也不知道,所以是0)
然後是兩行Selector 打頭的常量 (equ 定義出來是常量哦,像上面的dw 、dd 定義的纔是變量) ,這裏好像是選擇了GDT 中的某個符號到某個符號差值,一下子搞不明白了,我選擇暫時離開。
再往下走吧,在[SECTION .s16] 後面有個[BITS 16] ,下面應該就是一些16 位的代碼了吧?這些代碼做了什麼事?
它把ds (數據段)、es( 擴展段) 、ss( 堆棧段) 全搞成跟cs( 代碼段) 一樣值了( 這個值就由org 指定的0x7c00) ,還把sp( 堆棧指針) 指向0x100h (256) 處。然後eax 自身異或清零,代碼段指針被傳進去,左移4 位後與LABEL_SEG_CODE32 相加。
LABEL_SEG_CODE32 在哪裏?
LABEL_SEG_CODE32 在 [SECTION .s32] [BITS 32] 後面,是一段32 位代碼段的頭標籤。根據NASM 的規則我們知道,任何不帶[] 標籤或變量是代表一個地址,所以LABEL_SEG_CODE32 就是那段32 位代碼段的偏移地址。Cs 是16 位的,左移4 位再加上一個偏移地址不就是一個20 位的物理地址嗎!
這個20 位的物理地址又被分成了3 部分,被填入變態的GDT 的LABEL_DESC_CODE32 的第2 、4 、7 字節處,果真是初始化了一個GDT 。
eax 又改過自新了一次,與上面類似地,通過ds 竟然把變態的GDT 老大的地址弄到了,而且還把它存到了跟屁蟲GdtPtr 的第2 字節開始處. 。這下GdtPtr 什麼都沒做就有了那些GDT 的藏身基地,真是踏破鐵鞋無覓處,得來全不費工夫。
Lgdt 是個探子----- 一旦GdtPrt 有了GDT 們的基地消息,就向GDTR 報告。這樣一來,GDTR 就可以順滕摸瓜把GDT 們一個一個給揪出來了。究竟GDTR 是如何找到GDT 們的,下一節會有分解。
再看,in/out 做了一些端口操作,打通了地址線A20 ,然後又對控制器寄存器cr0 的第0 位做了些手腳,此時此刻,我們終於走到了保護模式的大門口!
鯉魚跳龍門,華麗轉身。
jmp 之後它就開始進入保護模式的天地了,在這片天地裏,它向人們展現了一個紅色的’P ’ ,最終死在了jmp $ 上。
百說不如一做,讓我們來見證一下這段輝煌。
第二章已經搭建好的bochs 和nasm 可以派上用場了:
nasm pmtest1.asm -o pmtest1.bin
dd if=pmtest1.bin of=a.img bs=512 count=1 conv=notrunc
bochs
注意:a.img 一定要用上次做好的那個,爲什麼?想一想。
現在,我們已經隱隱約約知道到了:
- 程序定義了一個叫做GDT 的數據結構。
- 後面的16 位代碼進行了一些與GDT 有關的操作。
- 程序最後跳到32 位代碼做了一點操作顯存的工作。
下一節,讓我們跟保護模式走得更近一些。