操作系统实践之第二章(实模式和保护模式)

在进入正文之前,我觉得有必要先交代一下实模式和保护模式之间的关系。

最早期的8086 CPU只有一种工作方式,那就是实模式,而且数据总线为 16位,地址总线为20位,实模式下所有寄存器都是16位。而从80286开始就有了保护模式,从80386开始CPU数据总线和地址总线均为32位,而且寄存器都是32位。但80386以及现在的奔腾、酷睿等等CPU为了向前兼容都保留了实模式,现代操作系统在刚加电时首先运行在实模式下,然后再切换到保护模式下运行。

好了,大致有个印象之后我们直接上代码,实现由实模式到保护模式的切换:

%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]
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
	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]

由上面的代码可见,进入保护模式的主要步骤:

1.   准备GDT           (其中的每一个描述符定义一个段)

2.   用lgdt加载gdtr  (将指定内容填入GDTR寄存器)

3.   打开A20       (打开该地址线后才可访问大于1M的地址)

4.   置cr0的PE位     (寄存器CR0的第0位是PE为,此位为0时,CPU运行于实模式;为1时,CPU运行于保护模式下)。

5.   跳转,进入保护模式(进入32位代码段执行)

 

每一段代码的主要意义已经有注释了。这里主要看一下导入文件"pm.inc" 的作用,它在上面这段代码中有用的语句如下:

;----------------------------------------------------------------------------
; 在下列类型值命名中:
;       DA_  : Descriptor Attribute
;       D    : 数据段
;       C    : 代码段
;       S    : 系统段
;       R    : 只读
;       RW   : 读写
;       A    : 已访问
;       其它 : 可按照字面意思理解
;----------------------------------------------------------------------------
; 描述符类型
DA_32			EQU	4000h	; 32 位段
DA_DPL0		EQU	  00h	; DPL = 0
DA_DPL1		EQU	  20h	; DPL = 1
DA_DPL2		EQU	  40h	; DPL = 2
DA_DPL3		EQU	  60h	; DPL = 3

; 存储段描述符类型
DA_DR			EQU	90h	; 存在的只读数据段类型值
DA_DRW		EQU	92h	; 存在的可读写数据段属性值
DA_DRWA		EQU	93h	; 存在的已访问可读写数据段类型值
DA_C			EQU	98h	; 存在的只执行代码段属性值
DA_CR			EQU	9Ah	; 存在的可执行可读代码段属性值
DA_CCO		EQU	9Ch	; 存在的只执行一致代码段属性值
DA_CCOR		EQU	9Eh	; 存在的可执行可读一致代码段属性值

; 宏 --------------------------------------------------------------------------------------------------
; 描述符
; 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 字节

对于这段头文件的内容其实最关键的就是理解最后定义的那段宏操作的意义,因为前面的那些描述符都是和最后的那个宏配套使用的。那么最后这段宏究竟在干什么呢?

先看一下段描述符的结构:

操作系统实践之第二章(实模式和保护模式)_第1张图片

对应最后的宏操作可以看出,该该宏操作就是将段基址、段界限、段属性分别填入不连续的对应位置中去(由上图段基址1、段基址2等类似的情形可知,其中的字段并不连续)。它的工作就是定义我们所需要的段描述符,其中的操作分别填入了段基址、段界限、段属性,从而,以后我们的代码就可以方便地使用该已定义的宏来产生段描述符了。

好了,现在用第一章的方法来运行上面给出的代码,其效果图如下:

操作系统实践之第二章(实模式和保护模式)_第2张图片

是的,出现最右边的那个红色的‘P’就是我们这次实验成功的象征。




你可能感兴趣的:(操作系统实践之第二章(实模式和保护模式))