注:本文配图来自:《64 ia 32 architectures software developer all manual》intel白皮书、《Orange‘s:一个操作系统的实现》、wiki.osdev.org
更多保护模式相关的知识可以翻阅intel白皮书 Volume3 Chapter 3 Protected-Mode Memory Management
什么是保护模式?
保护模式(Protected mode)是一种硬件资源保护方法,也包括内存保护,通过设置不同的级别来实现对应用程序操作限制,以保护计算机。同时在保护模式下,对内存的寻址能力也有了提升,从20位提升到了32位(注: 最大内存访问量受到硬件限制)。
进入保护模式
进入保护模式需要依次执行以下几个步骤:
- 准备GDT(Global Descriptor Table 全局描述符)
- 使用LGDT指令加载 GDT
- 打开A20总线
注:可以操作端口0x92h,来打开A20总线。0x92端口可能会被占用,可以通过BIOS提供的服务来开启A20总线,如果未成功则会返回错误。
BIOS:int 15h, ax=2401h 开启A2总线,ax=2400h 关闭A20总线。 - 设置CR0的PE位
- 使用JMP加载CS段寄存器,计算机正式保护模式
注:推出保护模式的步骤较为繁琐,以下方法可作参考:
- 准备16位模式的描述符(D/B=0,TYPE=2),描述符指向一段独立的16进制代码
- 独立代码段内,将其余段寄存器设置为16位模式的描述符并且CR0的PE标志位置零
- 跳回进入保护模式的段内,并恢复所有段寄存器在实模式中的值,然后关闭A20地址总线。
段描述符表
GDT表的格式实际上是段描述符的格式:
段描述符格式:
P位:存在(Present)位
- P=1 表示段在内存中存在
- P=0 表示段在内存中不存在
- DPL描述符:特权级别(Descriptor Privilege Level)。特权级可以是0、1、2或者3。数字越小特权级越大。
S位:指明描述符类型
- S=1 描述符是数据段/代码段描述符
- S=0 还是系统段/门描述符
- Type:描述符类型,根据描述符用法的不同,可以分为不同类型
参考以下表格
当描述符为数据段/代码段描述符时(S=1),描述符的定义如下:
当门描述符为系统段/门描述符时(S=0),type的定义可参考后文的门描述符。 G位:段界限粒度(Granularity)位。
- G=0时,段界限粒度为字节
- G=1时,段界限粒度为4KB。
D/B位:这一位比较复杂,分以下三种情况
在可执行代码段描述符中,这一位叫做D位。
- D=1时,在默认情况下指令使用32位地址及32位或8位操作数
- D=0时,在默认情况下使用16位地址及16位或8位操作数
在向下扩展数据段的描述符中,这一位叫做B位。
- B=1时,段的上部界限为4GB
- B=0时,段的上部界限为64KB。
在描述堆栈段(即描述符被SS所加载)的描述符中,这一位叫做B位。
- B=1时,隐式的堆栈访问指令(如push、pop和call)使用32位堆栈指针寄存器esp;
- D=0时,隐式的堆栈访问指令(如push、pop和call)使用16位堆栈指针寄存器sp。
- AVL:保留,为操作系统使用
- L:表示该描述符为ia-32e模式使用
GDTR
GDTR(Global Descriptor Table Registers 全局描述符寄存器),是一个专门用来控制GDT而存在的,通过lgdt指令可以加载数据。
,其内部的数据结构如下:
其中Size就是指定GDT表格的大小,而Offset则是GDT在内存中的位置,注意需要使用绝对地址(也就是段地址<<4+偏移值
得出的物理地址)。
Selector
选择子,其主体结构如下:
TI位:表示加载的描述符类型
- 0:GDT
- 1:LDT
- RPL:指定描述的权限级别,这需要与GDT描述中的DPL一判断,即那个权限小,那个生效。
CR0
Control Register0 控制寄存器0,这里不过多介绍其他位,只介绍PE位,指定CPU的运行状态,当置1时,表示CPU运行在32位保护模式下。
样例
程序执行成功后,会在屏幕的右下角显示一个红色的P
字符。由于引导扇区最大不能超过512Bytes,所以以下只演示进入保护模式。
下文使用nasm程序进行编译,编译完成后可以使用qemu或bochs来运行尝试。
org 0x7c00
jmp Label_start
; GDT表
GDT dd 0,0
GDTCode32 dd 0xffff, 0x409800 ;Base=0,Limit=ffffh,G=0,D/B=1,P=1,DPL=0,S=1,type=8
GDTVideo dd 0x80008000, 0x40920b ;Base=b8000h,Limit=8000h,G=0,D/B=1,P=1,DPL=0,S=1,type=2
GDTSize equ $ - GDT
GDTPtr:
dw GDTSize
dd 0
; 由于nasm不支持对标签进行运算,所以这里使用0填充,上方的GDTCode32也是一样,后面使用代码来填补
SelectorCode32 equ GDTCode32 - GDT
SelectorVideo equ GDTVideo - GDT
Label_start:
mov ax, cs
mov ds, ax
mov sp, 0x7c00
mov bp, sp
; 完善GDTPtr
xor eax, eax
mov ax, ds
shl ax, 4
add ax, GDT
mov [GDTPtr + 2], eax
; 完善GDTCode32
xor eax, eax
mov ax, cs
shl eax, 4
add eax, Label_Code32
mov [GDTCode32 + 2], ax
shr eax, 16
mov [GDTCode32 + 4], al
mov [GDTCode32 + 7], ah
; 着手进入保护模式
cli
in al, 92h
or al, 010b
out 92h, al
lgdt [GDTPtr]
mov eax, cr0
or al, 1
mov cr0, eax
sti
; 加载描述符
mov ax, SelectorVideo
mov es, ax
; 由于CS段寄存器的特殊性,这里需要使用jmp命令来加载
jmp SelectorCode32:0
[BITS 32]
Label_Code32:
; 显示字符'P'
mov al, 'P'
mov ah, 0ch
mov [es:(80*24+79)*2], ax
jmp $
times 510 - ($ - $$) db 0
dw 0xaa55
本文经「原本」原创认证,作者乾坤盘,访问yuanben.io查询【VW0M1DC7】获取授权信息。