结合64-ia-32-architectures-software-developer-manual-325462
的Volume3的9.9.1 Switching to Protected Mode
与国内的其他操作系统书籍对比,我将进入保护模式分为以下几步:
关闭中断。使用`cli`指令屏蔽外中断
打开A20
加载GDT
CR0.PE=1
使用jmp指令刷新流水线
初始化段选择子
其中,第五步一定要紧随在第四步之后。
具体代码实现在loader.asm中,如下:
;进入IA-32e准备工作:
;1.进入保护模式
;2.开启PAE
;3.开启PML4
;4.设置IA32_EFER.LME = 1
LOADER_BASE equ 0x900
GDT_BASE equ 0x500
org LOADER_BASE
LOADER_START:
;现在r8~r15、rax、es、cs、ss、ds、fs、gs、为0
;下面准备保护模式
;1.关闭中断
;2.打开A20
;3.加载GDT
;4.CR0.PE=1
;下面将GDT储存到0x500处,每个段描述符4+4个字节
mov bx,GDT_BASE
CODE_DESC:
mov dword [bx+8],0x0000ffff ;跳过第一个无效段描述符
mov dword [bx+12],0x00cf9800
DATE_STACK_DASC:
mov dword [bx+16],0x0000ffff
mov dword [bx+20],0x00cf9200
GDT_pointer:
mov word [bx+24],23 ;GDT界限为23
mov dword [bx+26],GDT_BASE ;GDT基址
;下面创建选择子
SELECTOR_CODE equ 1_000b
SELECTOR_DATA_STACK equ 10_000b
cli ;关闭中断
in al,0x92
or al,0000_0010b
out 0x92,al ;打开A20
lgdt [bx+24] ;加载GDT
mov eax,cr0
or eax,1
mov cr0,eax ;CR0.PE=1
jmp dword SELECTOR_CODE:Protect_start ;刷新流水线
[bits 32]
Protect_start:
mov ax,SELECTOR_DATA_STACK
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
mov ss,ax
mov esp,LOADER_BASE
hlt
;---------------保护模式初始化完成--------------------
上述代码将CS初始化为代码段选择子(第42行),将es、ss、ds、fs、gs都使用数据-栈选择子初始化。
代码运行
进入meOS目录,打开`start.cmd`,输入`make`,在打开bochs后进行调试,调试输出如下:
Next at t=0 (0) [0x0000fffffff0] f000:fff0 (unk. ctxt): jmpf 0xf000:e05b
; ea5be000f0 *<bochs:1> show mode* show mode switch: ON show mask is:
mode <bochs:2> c 00000362789: switched from ‘real mode’ to ‘protected
mode’ 00001713048: switched from ‘protected mode’ to ‘real mode’
00001713058: switched from ‘real mode’ to ‘protected mode’ 00001901775:
switched from ‘protected mode’ to ‘real mode’ *00006122383: switched
from ‘real mode’ to ‘protected mode’* Next at t=2879361142 (0)
[0x00000000095b] 0008:000000000000095b (unk. ctxt): add byte ptr
ds:[eax], al ; 0000 *<bochs:3> sreg* es:0x0010, dh=0x00cf9300,
dl=0x0000ffff, valid=1 Data segment, base=0x00000000, limit=0xffffffff,
Read/Write, Accessed cs:0x0008, dh=0x00cf9900, dl=0x0000ffff, valid=1
Code segment, base=0x00000000, limit=0xffffffff, Execute-Only,
Non-Conforming, Accessed, 32-bit ss:0x0010, dh=0x00cf9300,
dl=0x0000ffff, valid=1 Data segment, base=0x00000000, limit=0xffffffff,
Read/Write, Accessed ds:0x0010, dh=0x00cf9300, dl=0x0000ffff, valid=1
Data segment, base=0x00000000, limit=0xffffffff, Read/Write, Accessed
fs:0x0010, dh=0x00cf9300, dl=0x0000ffff, valid=1 Data segment,
base=0x00000000, limit=0xffffffff, Read/Write, Accessed gs:0x0010,
dh=0x00cf9300, dl=0x0000ffff, valid=1 Data segment, base=0x00000000,
limit=0xffffffff, Read/Write, Accessed ldtr:0x0000, dh=0x00008200,
dl=0x0000ffff, valid=1 tr:0x0000, dh=0x00008b00, dl=0x0000ffff, valid=1
*gdtr:base=0x0000000000000500, limit=0x17* idtr:base=0x0000000000000000,
limit=0x3ff *<bochs:4> info gdt* Global Descriptor Table
(base=0x0000000000000500, limit=23): GDT[0x00]=??? descriptor
hi=0x00000000, lo=0x00000000 GDT[0x01]=Code segment, base=0x00000000,
limit=0xffffffff, Execute-Only, Non-Conforming, Accessed, 32-bit
GDT[0x02]=Data segment, base=0x00000000, limit=0xffffffff, Read/Write,
Accessed You can list individual entries with ‘info gdt [NUM]’ or groups
with ‘info gdt [NUM][NUM]’ <bochs:5> q (0).[2879361142][0x00000000095b]
0008:000000000000095b (unk. ctxt): add byte ptr ds:[eax], al ; 0000
其中`show mode`用来显示虚拟机模式转换,`sreg`用来显示段寄存器的值,`info gdt`用来显示段描述符的值。
可以看到:
cs=0x0008=1_000b,而其余段寄存器的值也与选择子的值相同,说明选择子初始化成功。
gdtr
的值为0x500,段界限为0x17=23,这些与GDT_pointer的值相同,说明GDT初始化成功。gdt中共有3个段描述符,第零个废弃,第一个和第二个也与我们设置的相同,说明段描述符加载正确。
至此,保护模式加载完成