; ==========================================
; pmtest1.asm
; 编译方法:nasm pmtest1.asm -o pmtest1.bin
; ==========================================
%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]
11行中[SECTION .gdt] 表示一个段,通过[SECTION .###]将程序分成几个模块,使得程序结构更为清晰。
14-16行是全局描述符表GDT,其中包含三个描述符。
20行的结构与全局描述符表寄存器GDTR对应的数据结构一致,包含16位的界限和32位基地址。
38-45行用于初始化代码段描述符,确切地说是初始化代码段描述符中的基地址部分,其基地址部分是第2、3、4、7几个字节。到此为止,描述符LABEL_DESC_CODE32对应的即为[SECTION .s32]段的信息。
48-55行中将全局描述符表GDT的信息填充GdtPtr数据结构,并用lgdt载入GDTR中。
58行关中断是因为保护模式下的中断机制发生了变化,不关中断会发生异常。
61-63行用于打开地址线A20。这是历史问题,在8086中只有20位地址线,只能寻址1MB,如果试图访问超过1MB的地址时,会重新从地址开始寻址,造成兼容性问题。这里使用8042键盘控制器来控制A20地址位,默认情况是关闭的,现在将其打开。
67-69行中的cr0是内部寄存器,cr0的0位是PE位,当PE=0时,CPU为实模式,当PE=1时,CPU为保护模式,这三行代码就是修改CPU的运行模式为保护模式。也就是说,这三行代码之后的代码要根据保护模式的规矩办事了。
71行的dword是修饰偏移地址0的,告诉CPU启用32位程序计数器EIP。同时,装入CS的SelectorCode32是选择子,执行Jmp之后,系统将该选择子对应的描述符中的段基址装入隐藏在CS之后的寄存器中。
总结:
进入保护模式的步骤如下:
1 构筑GDT表项,并建立选择子对应关系,初始化段描述符。
2 准备GDT信息,并用lgdt加载入GDTR
3 打开A20
4 置cr0的PE位,跳转进入保护模式
就本节而言,保护模式的保护体现在段界限、段属性中,应该有所体会。