5.进入保护模式
结合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用来显示段描述符的值。
可以看到:
1.cs=0x0008=1_000b,而其余段寄存器的值也与选择子的值相同,说明选择子初始化成功。
2.gdtr的值为0x500,段界限为0x17=23,这些与GDT_pointer的值相同,说明GDT初始化成功。
3.gdt中共有3个段描述符,第零个废弃,第一个和第二个也与我们设置的相同,说明段描述符加载正确。