认识保护模式
为什么需要保护模式
- Intel 8086是16位CPU,它有着16位的寄存器,16位的数据总线以及20位的地址总线和1MB的寻址能力。
- 从80386开始CPU进入32位时代,寻址能力达到4GB,无法使用16位寄存器完成寻址
GDT(global descriptor table)
而保护模式下,虽然段值仍然由原来16位的cs、ds等寄存器表示,但此时它仅仅变成了一个索引,这个索引指向一个数据结构的一个表项,表项中详细定义了段的起始地址、界限、属性等内容。这个数据结构,就是GDT.
描述符(Descriptor)
GDT的作用是用来提供段式存储机制,这种机制是通过段寄存器和GDT中的描述符共同提供的。
描述符的结构
- 段基址:规定线性地址空间中段的开始地址。在保护模式下,段基地址长32位。因为基地址长度与寻址地址的长度相同,所以段基地址可以是 0~4GB 范围内的任意地址,而不像实方式下规定的边界必须被16整除。
- 段界限:段界限规定段的大小。在保护模式下,段界限决定了偏移量的最大值。对于向下扩展的段,如堆栈段来说, 段界限决定了偏移量的最小值。
[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]
在pmtest1.asm
中定义了如上的GDT,包括:
- 三个描述符:
空描述符
非一致代码段
显存首地址
,并赋予段基址
段界限
属性
的初值 - GDT长度
- GDT指针:包括GDT界限和GDT基地址
- GDT选择子:包括
非一致代码段选择子
显存首地址选择子
选择子(selector)
; GDT 选择子
SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT
SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT
对于上面的代码片段,在Ti
和RPL
都是0的时候,选择子可以看做是描述符相对于GDT基地址的偏移
段式寻址
理解[SECTION .s16]
程序的关键是要明白此时CPU仍然处于实模式,此时最大的寻址范围仍然是1M,因此可以用cs
寄存器存储
[SECTION .s16]
[BITS 16]
LABEL_BEGIN:
mov ax, cs ;cs为0
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0100h
这一节程序是用cs中存储的段基址(即LABEL_BEGIN
标签在内存中对应的地址)初始化ds
es
ss
寄存器,并初始化sp
寄存器
; 初始化 32 位代码段描述符
xor eax, eax
mov ax, cs
shl eax, 4 ;段基址(CS)左移四位
add eax, LABEL_SEG_CODE32 ;加上偏移地址(LABEL_SEG_CODE32)
这段程序利用如下公式算出LABEL_SEG_CODE32
真实的逻辑地址并存储在eax中(此时仍然处于实模式,计算机的最大寻址空间仍然是1M)
逻辑地址
=
段基址
∗
16
+
偏移地址
逻辑地址=段基址*16+偏移地址
逻辑地址=段基址∗16+偏移地址
;对照图3.4阅读
mov word [LABEL_DESC_CODE32 + 2], ax;BYTE2,3
shr eax, 16
mov byte [LABEL_DESC_CODE32 + 4], al;BYTE4
mov byte [LABEL_DESC_CODE32 + 7], ah;BYTE7
这段程序则是将eax
(LABEL_SEG_CODE32
的真实逻辑地址)赋值给BYTE2
BYTE3
BYTE4
BYTE7
作为保护模式下描述符LABEL_DESC_CODE32
的基地址
; 为加载 GDTR 作准备
xor eax, eax
mov ax, ds ;ds = cs
shl eax, 4
add eax, LABEL_GDT ; eax <- gdt 基地址
mov dword [GdtPtr + 2], eax ; GDT基地址([GdtPtr + 2]) <- gdt 基地址
这段程序是将GDT
的信息写入GDTPtr
指针
首先计算出LABEL_GDT
对应的内存地址,然后将GdtPtr
对应的基地址赋值为LABEL_GDT
的真实的内存地址
lgdt [GdtPtr]
这段程序是将GdtPtr
加载到gdtr
寄存器当中,gdtr
寄存器的格式如下图所示
; 在保护模式下,如果不关终端程序会发生错误
cli
; 打开地址线A20,才能访问所有的内存空间
in al, 92h
or al, 00000010b
out 92h, al
- 问:什么是A20?
- 答:这是一个历史问题。
- 8086中,“段:偏移”这样的模式能表示的最大内存是FFFF:FFFF,即10FFEFh。可是8086只有20位的地址总线,只能寻址到1MB,那么如果试图访问超过1MB的地址时会怎样呢?实际上系统并不会发生异常,而是回卷(wrap)回去,重新从地址零开始寻址。可是,
- 到了80286时,真的可以访问到1MB以上的内存了,如果遇到同样的情况,系统不会再回卷寻址,这就造成了向上不兼容.
- 为了保证百分之百兼容,IBM想出一个办法,使用8042键盘控制器来控制第20个(从零开始数)地址位,这就是A20地址线,如果不被打开,第20个地址位将会总是零。
; 准备切换到保护模式
mov eax, cr0
or eax, 1
mov cr0, eax
; 真正进入保护模式
jmp dword SelectorCode32:0 ; 执行这一句会把 SelectorCode32 装入 cs,
; 并跳转到 Code32Selector:0 处
这段程序首先通过控制cr0寄存器的第0位(PE标志位:1为实模式,0为保护模式)切换到保护模式
然后使用jmp dword SelectorCode32:0
跳转到实模式,将SelectorCode32
即LABEL_SEG_CODE32
标志的真实物理地址当做保护模式的段基址