3.1 认识保护模式

32位PC机的工作模式

IA32 下,CPU有两种工作模式:
① 实模式(Real-Addressed Mode)

② 保护模式(Protected Mode) :

PC刚加电打开或系统复位后,工作在实模式下,它为保护模式所需的数据结构做好各种配置和准备。之后,修改控制寄存器CR0中的保护模式允许位PE,使得 PE=1 ,从而让CPU进入保护模式;当 PE=0 时则返回实模式。

上面提到的16位到32位的革命性转换,就是代码中从16位跳转到32位代码段的那个历史性的 jmp 。

从实模式到保护模式

实模式中,8086 为16位的CPU、寄存器、数据总线和20位的地址总线(1MB的寻址能力),一个逻辑地址由段(16位)和偏移(16位)两部分组成,段地址是地址的一部分,表示以xxxx0h开始的一段内存,物理地址=段基地址*16+偏移地址。

但是到了32位时代,寻址空间到了4GB,原来的16位寄存器已经不够用了。为此,我们需要保护模式,目的之一就是提供更大的寻址能力

32位时代的地址仍然可以用段值:偏移来表示,只是段的概念发生了根本性的变化,虽然段值仍然由原来16位的 cs,ds 等段寄存器表示,但是它们已经变成了一个索引,指向数据结构GDT的一个表项,表项中详细定义了段的起始地址、界限、属性等内容,表项的名字是描述符(Descriptor)。

即,GDT的作用是提供段式存储机制,这种机制由段寄存器+GDT中的描述符共同构成。

描述符、选择子结构和寻址方式

下面是代码段和数据段描述符的结构图:

此外,还有系统段描述符和门描述符。

本节代码 pmtest1.asm 中GDT段定义了三个描述符,可以分别称为 DESC_DUMMY, DESC_CODE32, DESC_VIDEO 。GDT中每一个描述符都定义了一个段,其中 DESC_VIDEO 指向的是显存。

它们如何和16位的 cs,ds,es,gs 等段寄存器对应起来,使这些段寄存器成为相对于GDT的一个索引呢?在 [SECTION .s32] 中有这样的代码:

1
2
mov ax, SelectorVideo
mov gs, ax

在前面的GDT段中,定义了 SelectorVideo :

1
2
3
; GDT 选择子
SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT
SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT

由此,段寄存器 gs 的值变成了 SelectorVideo 标号地址,SelectorVideo 则似乎是 DESC_VIDEO 相对于GDT段基址 LABEL_GDT 的一个偏移,即选择子。当然,选择子不完全是偏移,其结构如下:

当最低的三位 TL,RPL 都为零时,选择子真正成为对应描述符相对于GDT段基址的偏移。

这样,我们明白了这些代码的意义,gs 段寄存器值为选择子 SelectorVideo ,它指向GDT中对应显存的描述符 DESC_VIDEO ,然后下面的32位代码段,将 ax 的值写入到显存中偏移位 edi 的位置(段:偏移中,偏移地址的概念没有变化)。

1
2
3
4
5
6
7
8
9
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
...

从 s32 这部分代码,目前我们知道的段式寻址方式如下,逻辑地址(段:偏移)经过段机制(段选择子和段描述符)变成线性地址(Linear Address),这里的线性地址可以看做是“物理地址”:

描述符属性

下面详细介绍段描述符的几个属性:

  • P 位 (Present) 存在位,为 1 表示段存在于内存中,否则段不在内存中;

  • DPL (Descriptor Privilege Level) 描述符特权级位, 0~3 ,数字越小特权级越大;

  • S 位指明描述符是数据段/代码段(S=1),还是系统段/门描述符(S=0) ;

  • TYPE 描述符,0~15:

  • G 位(Granularity)段界限粒度位,当 G=0 时段界限粒度为字节,否则为 4KB ;

  • D/B 位:

    • 可执行代码段描述符中,是 D 位,D=1 时指令默认使用32位地址及32位/8位操作数;D=0 时默认使用16位地址及16位/8位操作数;

    • 向下扩展数据段描述符中,是 B 位,B=1 时段的上部界限是 4GB ;否则是 64KB ;

    • 堆栈段时,B=1 时隐式堆栈访问指令(如 push,pop,call )使用32位堆栈指针寄存器 esp ;B=0 时隐式堆栈访问指令使用16位堆栈指针寄存器 sp 。

  • AVL 保留位,可以被系统软件使用。

一致代码段 Conforming Code Segment:

  • 一致:向特权级更高的一致代码段转移时,当前特权级会延续下去;而向特权级更高的非一致代码段转移时会报错(general-protection exception,常规保护错误),除非使用调用门或者任务门。

    如果系统代码不访问受保护的资源和某些类型的异常处理,可以放入一致代码段中,此时低特权级的程序可以访问高特权级的一致代码段;为了防止低特权级的程序访问,需要保护的系统代码则应该放入非一致代码段;

  • 目标代码是低特权级,则无论其是否是一致代码段,都不能通过jmp或call转移访问。这样也会导致常规保护错误;

  • 相同特权级的代码,可以直接访问,无论是否是一致代码段。

  • 特别注意的是,所有数据段都是非一致的,即不可能被低特权级的代码访问;但是它可以被更高特权级和同特权级的代码访问,不用使用特定的门(低->高)。