有了保護模式的理論基礎,今天就來實站一下,沒有看的一定要看,在結合下面程式的分析,一定就會懂的,下面程式我又加了一些注釋,理論上應該沒有什麼問題了。
; ==========================================
; pmtest1.asm
; 編譯方法:nasm pmtest1.asm -o pmtest1.bin
; ==========================================
DA_32 EQU 4000h ; 32 位段
DA_C EQU 98h ; 存在的隻執行代碼段屬性值
DA_DRW EQU 92h ; 存在的可讀寫資料段屬性值
ATCE32 EQU 4098h ;存在的隻執行32代碼段屬性值
;下面是存儲段描述符的宏定義
; 有三個參數:段界限,段基址,段屬性其中宏定義中的%1代表參數1,%2代表參數2,%3代表參數3
%macro Descriptor 3
dw %2 & 0FFFFh ; 段界限1(參數2的低16位)
dw %1 & 0FFFFh ; 段基址1(參數1的低16位)
db (%1 >> 16) & 0FFh ; 段基址2(參數1的16-23位)
dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) ; 屬性1(高4位) + 段界限2(高4位) + 屬性2(低8位)
db (%1 >> 24) & 0FFh ; 段基址3(參數1的24-31位)
%endmacro ; 共 8 位元組
;段界限共20位,段基位址30位,段屬性共16位(含段界限高4位)
org 07c00h
jmp LABEL_BEGIN
[SECTION .gdt] ;section是告訴編譯器劃分出一個叫gdt的段
; GDT
; 段基址, 段界限 , 屬性
LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符
LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + DA_32; 非一緻代碼段(DA_C + DA_32=ATCE32)
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]
LABEL_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 ;eax*16==段值*16(實模式下不用手工計算,但是這裡需要自己計算)
add eax, LABEL_SEG_CODE32;加上LABEL_SEG_CODE32偏移,這樣eax->LABEL_SEG_CODE32基位址
mov word [LABEL_DESC_CODE32 + 2], ax ;設定基位址1(0-15位)
shr eax, 16
mov byte [LABEL_DESC_CODE32 + 4], al ;設定基位址2(16-23位)
mov byte [LABEL_DESC_CODE32 + 7], ah ;設定基位址3(24-31位)
; 為加載 GDTR 作準備
xor eax, eax
mov ax, ds
shl eax, 4 ;eax*16==段值*16
add eax, LABEL_GDT ; eax <- gdt 基位址
mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基位址
; 加載 GDTR
lgdt [GdtPtr] ;将GdtPtr所指的内容送到GDT寄存器
; 關中斷
cli
; 打開位址線A20
in al, 92h
or al, 00000010b
out 92h, al
; 準備切換到保護模式
mov eax, cr0
or eax, 1 ;設定cr0的0位(PE位,PE=1準備進入保護模式)
mov cr0, eax ;更新cr0
; 真正進入保護模式
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]
我覺得注釋挺詳細了,但是還是來分析一下吧
(1)常量定義怎麼來的?
DA_32 EQU 4000h ; 32 位段
DA_C EQU 98h ; 存在的隻執行代碼段屬性值
還記得這個圖嗎?常量就是從這個圖中産生的。
4000h=0100000000000000b --->屬性位D=1表示段的上界是4G,是以是32位段
98h =0000000010011000b --->p=1表示描述符對位址轉換是有效的(存在位),DPL特權級=0,DT=1表示系統段,TYPE=8表示隻執行
(2)存儲段描述符解釋(宏定義)
;下面是存儲段描述符的宏定義
; 有三個參數:段界限,段基址,段屬性其中宏定義中的%1代表參數1,%2代表參數2,%3代表參數3
%macro Descriptor 3
dw %2 & 0FFFFh ; 段界限1(參數2的低16位)
dw %1 & 0FFFFh ; 段基址1(參數1的低16位)
db (%1 >> 16) & 0FFh ; 段基址2(參數1的16-23位)
dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) ; 屬性1(高4位) + 段界限2(高4位) + 屬性2(低8位)
db (%1 >> 24) & 0FFh ; 段基址3(參數1的24-31位)
%endmacro ; 共 8 位元組
;段界限共20位,段基位址30位,段屬性共16位(含段界限高4位)
段界限共20位,段界限1取其低16位,是以和0ffffh與。
段基位址1同理
段基位址2:16-23位,将段基位址右移16位,得到段基位址高16位,和0ffh與得到低8位(也就是16-23位)。
段基位址3同理
段屬性:屬性1(高4位) + 段界限2(高4位) + 屬性2(低8位)看第一個圖,(%2 >> 8) & 0F00h)為屬性1,(%3 & 0F0FFh)為段界限2(高4位) + 屬性2(低8位),當然也可以這樣寫((%2 >> 8) & 0F00h) | (%3 & 0F000h)| (%3 & 000FFh)
這樣是不是清楚很多了。
[SECTION .gdt]告訴編譯器劃分出一個叫gdt的段(資料段)
[SECTION .s16],[SECTION .s32]為兩個代碼段
; GDT
; 段基址, 段界限 , 屬性
LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符
LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + DA_32; 非一緻代碼段(DA_C + DA_32=ATCE32)
LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW ; 顯存首位址
; GDT 結束
LABEL_GDT(空描述符),LABEL_DESC_CODE32(32位代碼段描述符),LABEL_SESC_VIDEO(顯示記憶體描述符)。每個描述符都包含了3個屬性,段基址、段界限、段屬性。将三個描述符組織到一起構成一個全局段描述符表(GDT)。
GdtLen equ $ - LABEL_GDT ; GDT長度
GdtPtr dw GdtLen - 1 ; GDT界限
dd 0 ; GDT基位址
GDTR示意圖:
GdtPtr是不是和GDTR長得一樣呀,GdtLen是GDT的長度。GdtPtr為一個資料結構,包含兩個元素,第一個元素是2 bytes的GDT界限。第二個元素是4 bytes的GDT的基位址。該資料結構與全局描述符表寄存器(GDTR)的資料結構相同,是以在加載GDTR的時候(源代碼55行),就是将該GdtPtr加載到GDTR中。
; 段基址, 段界限 , 屬性
LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符
LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + DA_32; 非一緻代碼段(DA_C + DA_32=ATCE32)
LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW ; 顯存首位址
由于第一個段LABEL_GDT是空描述符,它僅僅代表該GDT的初始位址,是以該描述符為空描述符,一般情況下,不為它建立選擇子。然後該程式建立了兩個選擇子SelectorCode32和SelectorVideo,分别對應着這兩個段LABEL_SESC_CODE32和LABEL_DESC_VIDEO, 一緻和非一緻後面會解釋的。
在保護模式下,首先使用段選擇子在段描述符表中查找到相對應的段描述符,找到32位段基址,然後在與32位的偏移量相加,得到線性位址。段基址和段偏移量都是32位的,是以尋址範圍大小為4GB。在程式中jmp dword SlectorCode32:0的作用,就是進入保護模式下的尋址方式。其中,在使用某個段時,它的段選擇子是存儲在段寄存器中的。
剩下的在代碼中解釋得很清楚了,這樣應該清楚了吧
下面總結一下進入保護模式的主要步驟:
1.準備GDT
2.用lgdt加載gdtr
3.打開A20
4.置cr0的PE位
5.跳轉,進入保護模式
注:使用在上次建立的軟碟鏡像,如果新建立,那麼必須在結尾的地方加上aa55h标志,我開始就沒有意識到,總是失敗,後面自己在後面加結束标記但是沒有成功,最後還是用的上次建立的鏡像。
參考:
《Orange's 一個作業系統的實作》學習筆記--保護模式理論初步(二)
《Orange's 一個作業系統的實作》學習筆記--保護模式理論初步(一)