天天看点

《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 一个操作系统的实现》学习笔记--保护模式理论初步(一)