從時模式到保護模式,是計算法技術跨時代的發展。大家想想笨拙的Dos界面,黑底白字的那種冷漠界面到win95各種色彩斑斓的視窗,兩者之間的差別其實就是實模式和保護模式的天壤之别。
保護模式中,最重要的一個概念莫過于”保護”二字,有了“保護”功能後,CPU為軟體提供了很多的功能,當然也有了更多的限制。要詳細解析保護機制,沒有幾千字的“鴻篇巨制”是不可能的,但我想你和我一樣不會有那樣的耐心,由此話不多說,我們先将代碼跑起來再說:
%include "pm.inc"
org
jmp LABEL_BEGIN
[SECTION .gdt]
; 段基址 段界限 屬性
LABEL_GDT: Descriptor , ,
LABEL_DESC_CODE32: Descriptor , SegCode32Len - , DA_C + DA_32
LABEL_DESC_VIDEO: Descriptor B8000h, ffffh, DA_DRW
GdtLen equ $ - LABEL_GDT
GdtPtr dw GdtLen -
dd
SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT
SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT
[SECTION .s16]
[BITS ]
LABEL_BEGIN:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, h
xor eax, eax
mov ax, cs
shl eax,
add eax, LABEL_SEG_CODE32
mov word [LABEL_DESC_CODE32 + ], ax
shr eax,
mov byte [LABEL_DESC_CODE32 + ], al
mov byte [LABEL_DESC_CODE32 + ], ah
xor eax, eax
mov ax, ds
shl eax,
add eax, LABEL_GDT
mov dword [GdtPtr + ], eax
lgdt [GdtPtr]
cli ;關中斷
in al, h
or al, b
out h, al
mov eax, cr0
or eax ,
mov cr0, eax
jmp dword SelectorCode32:
[SECTION .s32]
[BITS ]
LABEL_SEG_CODE32:
mov ax, SelectorVideo
mov gs, ax
mov si, msg
mov ebx,
mov ecx,
showChar:
mov edi, (*)
add edi, ebx
mov eax, edi
mul ecx
mov edi, eax
mov ah, ch
mov al, [si]
cmp al,
je end
add ebx,
add si,
mov [gs:edi], ax
jmp showChar
end:
jmp $
msg:
DB "Protect Mode",
SegCode32Len equ $ - LABEL_SEG_CODE32
我們再看看pm.inc的内容:
%macro Descriptor
dw %2 & 0FFFFh
dw %1 & 0FFFFh
db (%1>>) & 0FFh
dw ((%2 >> ) & 0F00h) | (%3 & 0F0FFh)
db (%1 >> ) & 0FFh
%endmacro
DA_32 EQU h ; 位段
DA_C EQU h ; 存在的隻執行代碼段屬性值
DA_DRW EQU h ; 存在的可讀寫資料段屬性值
把上面的代碼通過以下指令編譯:
nasm -o kernel.bat kernel.asm
在目錄下會生成一個核心檔案,kernel.bat,運作java代碼,程式會将生成的核心寫入到虛拟軟碟中,将虛拟軟碟加載到虛拟機中,運作後會得到如下結果:
我們看看代碼,從LABEL_SEG_CODE32:這一部分開始,代碼就執行在保護模式下,這段代碼的作用就是顯示一串字元,gs是計算機的一個寄存器,它跟eax,ebx這些寄存器差不多,但作用更為單一,主要用來指向顯存,當我們将資訊寫入gs指向的記憶體後,資訊會顯示到螢幕上。用于顯示字元的顯存,記憶體位址從0XB800h開始,從該位址開始,每兩個位元組用來在螢幕上顯示一個字元,這兩個位元組中,第一個位元組的資訊用來表示字元的顔色,第二個位元組用來存儲要顯示的字元的ASCII值,螢幕一行能顯示80個字元,大家看到代碼中有語句:
mov edi, (80*11)
這表明我們要從第11行開始顯示字元,接下來又有語句:
add edi, ebx
其中,ebx的值是11,這表明我們要從第11行的第10列開始顯示字元串,接下來的語句是:
mov eax, edi
mul ecx
ecx的值是2,這個2就是我們前面說過的顯示一個字元需要兩個位元組,上面幾句彙編語句的作用是:
eax = ((80*11) + 10) * 2
這樣eax就指向了第11行第11列所在的顯存位置,接下來語句:
mov ah, 0ch
它的作用是在用來顯示字元的兩位元組中,對第一個位元組放入數值0ch,也就是設定字元的顔色,接下來的語句:
mov al, [si]
将寄存器si指向的字元的ascii值寫入到第二個位元組,這樣,字元就顯示到螢幕上了。大家注意寄存器si的用法:[si]. si相當于C語言中的一個指針,指向記憶體某個位址,[si]就是讀取si指向的記憶體位址的資訊,等同于 c語言中的*(si)
以上都是小細節,真正的要點是,我們要了解什麼叫保護模式。我們先看保護模式的兩個顯著特點:
1.尋址空間從時模式的1M增強到4G
2.不同的代碼擁有不同的優先級,優先級高的能夠執行特殊指令,優先級低的,某些重要指令就無法執行。
于是進入保護模式,我們需要解決兩個問題,一是如何擷取超過1M以上的記憶體位址,第二是如何設定不同代碼所具有的優先級。我們先看看尋找能力的變化,在實模式下,cpu是16位的,寄存器16位,資料總線16位,位址總線20位,于是尋找的範圍必然受限于20位的位址總線,是以尋找範圍無法超過1M(2^20).要想實作4GB的尋址,我們必須使用32位來表示位址,intel是這麼解決這個問題的,他們用連續的8個位元組組成的結構體來解決一系列問題:
byte0
byte1
…..
byte7
其中,位元組2,3,4以及位元組7,這四個位元組合在一起總共有32位,這就形成了一個32位的位址。同時把位元組0,位元組1,以及将位元組6的拆成兩部分,各4個bits,前4個bits跟位元組0,位元組1合在一起,形成一個20個bit的資料,用來表示要通路的記憶體長度。這樣,我們就解決了記憶體尋址的問題。
大家或許猜到,pm.inc裡面的宏定義就是我們說的7位元組資料結構,
%macro Descriptor 3
表示要初始化該資料結構,需要傳入3個參數,%1表示引用第一個參數,%2表示引用第二個參數。初始化該結構時,輸入的一個參數是記憶體的位址,大家看語句:
dw %1 & 0FFFFh
db (%1>>16) & 0FFh
這兩句就是把記憶體位址的頭三個位元組放入到byte2,byte3,byte4,最後一句:
db (%1 >> 24) & 0FFh
就是講位址的第4個位元組放入到byte7. 初始化資料結構的第二個參數表示的是要通路的記憶體的長度,大家看語句:
dw %2 & 0FFFFh
就是把記憶體長度的頭兩個位元組寫入byte0,byte1,語句:
dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)
中的((%2 >> 8) & 0F00h)就是把記憶體長度的第16-19bit寫入到byte6的前4個bit.由此要通路的記憶體和記憶體的長度就都設定好了,由于保護模式是一個非常複雜的邏輯,為了掌握它,我們一次隻吃透一點,這樣才好掌握,本章,我們隻要了解如何進行32位的尋找就足夠了,其他的知識我們後面會一點一滴的分析。
大家看開頭的幾條語句:
xor eax, eax
mov ax, cs
shl eax,
add eax, LABEL_SEG_CODE32
mov word [LABEL_DESC_CODE32 + ], ax
shr eax,
mov byte [LABEL_DESC_CODE32 + ], al
mov byte [LABEL_DESC_CODE32 + ], ah
LABEL_SEG_CODE32是一段代碼的起始位址,上面的語句就是将這個起始位址寫入到byte2,byte3,byt4,和byte7.大家是否會疑惑,為什麼不在初始化時将這個位址直接傳進去呢,例如:
LABEL_DESC_CODE32: Descriptor LABEL_SEG_CODE32, SegCode32Len - 1, DA_C + DA_32
這是因為,結構體初始化時隻能傳入常量,LABEL_SEG_CODE32所代表的數值需要編譯器在将代碼編譯完後才能計算出來,是以LABEL_SEG_CODE32一開始的值還不能确定,是以不能直接用于初始化結構體。
以上的代碼大家可以通過指令擷取:
git clone https://github.com/wycl16514/OS-Kernel-from-real-to-protected-mode.git
大家看完這一章後,可能有一點感性認識,但更有可能是落入到一種雲裡霧裡,不知所措的狀态,這不要緊,在後面,我還會有很多篇幅來解釋保護模式,大家的迷惑會在後面的閱讀中一點一點的給驅散掉,面對一個複雜棘手的問題,将其分解,各個擊破是最好的政策,隻要我們有耐心,一步一個腳印,我們就會經曆一個山窮水盡疑無路,柳暗花明又一村的體驗。