6.保护模式2

保护模式二

保护模式的保护主要体现在段描述符的属性字段中,这些属性只是用来描述一块内存的性质,是用来给 CPU 做参考的,当有实际行动在这片内存上发生时,CPU 用这些属性来检查动作的合法性,从而起到保护的作用。

一、向段寄存器加载选择子时的保护

当引用一个内存段时,就是往段寄存器中加载个选择子,为了避免出现非法引用内存段的情况,CPU 会做如下检查:

1. 首先根据选择子的值验证段描述符是否超越界限

处理器先检查选择子 TI 位的值,如果为 0,则从全局描述符表寄存器 gdtr 中拿到 GDT 基地址和 GDT 界限值。如果 TI 为 1,则从局部描述符表寄存器 ldtr 获取。

CPU 需要保证选择子是正确的,判断的标准是选择子的索引值一定要小于等于描述符表(GDT 或 LDT)中描述符的个数。段描述符(8字节)的最后 1 字节一定要在描述符表的界限地址之内,因此索引值要满足表达式:描述符表基地址 + 选择子中的索引值 * 8 + 7 <= 描述符表基地址 + 描述符表界限值

GDT 的第 0 个描述符是空描述符。虽然可以往寄存器中加载值为 0 的选择子,但在使用时 CPU 将抛出异常。

2. 段描述符中有个 type 字段,主要时检查段寄存器的用途和段类型是否匹配。

  • 只有具有可执行属性的段(代码段)才能加载到 CS 段寄存器中
  • 只具备执行属性的段(代码段)不允许加载到初 CS 外的段寄存器中
  • 只有具备可写属性的段(数据段)才能加载到 SS 栈段寄存器中
  • 至少具备可读属性的段才能加载到 DS、ES、FS、GS 段寄存器中

3. 还需要检查段是否存在。CPU 通过段描述符中的 P 位来确认内存段是否存在。

  • 如果 P 位为 1,则表示存在,这时候就可以将选择子载入段寄存器了,同时段描述符缓冲寄存器也会更新为选择子对应的段描述符的内容,随后 CPU 将段描述符中 type 字段的 A 位置为 1,表示 CPU 已经访问过了。
  • 如果 P 位为 0,则表示该内存段不存在,不存在的原因可能是由于内存不足,操作系统将该段移出内存转储到硬盘上了,这时候 CPU 会抛出异常,自动转去执行相应的异常处理程序,异常处理程序将段从硬盘加载到内存后并将 P 位置为 1,随后返回。CPU 继续执行刚才的操作,判断 P 位。

总结来说,P 位是由操作系统来设置,由 CPU 来检查。A 位由 CPU 来设置。

二、代码段和数据段的保护

CPU 每访问一个地址,都要确认该地址不能超过其所在内存段的范围。

对于数据段和代码段,访问内存都需要用分段策略,即“段基址:段内偏移地址” 的形式,在 32 位保护模式下,段基址放在 CS 寄存器中,段内偏移地址,即有效地址,存放在 EIP 寄存器中。

CS:EIP 只是指令的起始地址,指令本身也有长度,因此指令也应该完整的落在段中;当然数据也是有长度的,CPU 也要保证操作数要完整的落在数据段中。否则 CPU 会抛出异常。

三、栈段的保护

段描述符 type 中的 E 位用来表示段的扩展方向。这也就引出了栈段,他是向下扩展,即地址越来越小。

  • 对于向上扩展的段,实际的段界限是段内可以访问的最后一字节
  • 对于向下扩展的段,实际的段界限是段内不可以访问的第一个字节

栈的段界限是以栈段的基址为基准的,并不是以栈底,因此栈的段界限肯定是位于栈顶之下。地址本身由低向高发展,段界限也是个地址,而栈的扩展方向是由高地址向低地址,与段界限有个碰撞的趋势。为了避免碰撞,将段界限地址 +1 视为栈可以访问的下限。也就是说,段界限+1,才是栈指针可达的下边界。