天天看點

《Orange's 一個作業系統的實作》學習筆記--實踐認識保護模式

有了保護模式的理論基礎,今天就來實站一下,沒有看的一定要看,在結合下面程式的分析,一定就會懂的,下面程式我又加了一些注釋,理論上應該沒有什麼問題了。

; ==========================================
; 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	; 存在的隻執行代碼段屬性值
           
《Orange's 一個作業系統的實作》學習筆記--實踐認識保護模式

還記得這個圖嗎?常量就是從這個圖中産生的。

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位)
           
《Orange's 一個作業系統的實作》學習筆記--實踐認識保護模式

段界限共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示意圖:

《Orange's 一個作業系統的實作》學習筆記--實踐認識保護模式

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的作用,就是進入保護模式下的尋址方式。其中,在使用某個段時,它的段選擇子是存儲在段寄存器中的。

剩下的在代碼中解釋得很清楚了,這樣應該清楚了吧

《Orange's 一個作業系統的實作》學習筆記--實踐認識保護模式

下面總結一下進入保護模式的主要步驟:

1.準備GDT

2.用lgdt加載gdtr

3.打開A20

4.置cr0的PE位

5.跳轉,進入保護模式

注:使用在上次建立的軟碟鏡像,如果新建立,那麼必須在結尾的地方加上aa55h标志,我開始就沒有意識到,總是失敗,後面自己在後面加結束标記但是沒有成功,最後還是用的上次建立的鏡像。

參考:

《Orange's 一個作業系統的實作》學習筆記--保護模式理論初步(二)

《Orange's 一個作業系統的實作》學習筆記--保護模式理論初步(一)