保护模式

介绍

当 80286 发明时,它支持传统的 8086 分段(现在称为实模式),并添加了一种称为保护模式的新模式。此模式已经在每个 x86 处理器中使用,尽管通过各种改进(例如 32 位和 64 位寻址)进行了增强。

设计

在保护模式下,完全取消了简单的将地址添加到移位段寄存器值。他们保留了 Segment 寄存器,但是他们不是使用它们来计算地址,而是使用它们索引到一个表(实际上是两个……中的一个),它定义了要访问的段。这个定义不仅描述了 Segment 在内存中的位置(使用 Base 和 Limit),还描述了它是什么类型的 Segment(代码,数据,堆栈甚至系统)以及哪些程序可以访问它(OS Kernel,正常程序) ,设备驱动程序等)。

段寄存器

每个 16 位段寄存器采用以下形式:

+------------+-----+------+
| Desc Index | G/L | Priv |
+------------+-----+------+
 Desc Index = 13-bit index into a Descriptor Table (described below)
 G/L        = 1-bit flag for which Descriptor Table to Index: Global or Local
 Priv       = 2-bit field defining the Privilege level for access

全局/本地

全局/本地位定义访问是进入全局描述符表(不出所料地称为全局描述符表或 GDT),还是本地描述符表(LDT)。LDT 的想法是每个程序都有自己的描述符表 - 操作系统定义一组全局段,每个程序都有自己的本地代码,数据和堆栈段。操作系统将管理不同描述符表之间的内存。

描述表

每个描述符表(全局或本地)是一个 64K 数组,包含 8,192 个描述符:每个 8 字节的记录定义了它描述的段的多个方面。段寄存器的描述符索引字段允许 8,192 个描述符:没有巧合!

描述

描述符保存了以下信息 - 请注意,描述符的格式随着新处理器的发布而改变,但每个处理器都保留了相同的信息:

  • Base
    这定义了内存段的起始地址。
  • 限制
    这定义了内存段的大小 - 排序。他们必须做出决定:0x0000 的大小是否意味着 0 的大小,所以无法访问?还是最大尺寸?
    相反,他们选择了第三个选项:限制字段是细分中的最后一个可寻址位置。这意味着可以定义一个单独的段; 或者地址大小的最大大小。
  • 类型
    有多种类型的段:传统的代码,数据和堆栈(见下文),但也定义了其他系统段:
    • 本地描述符表段定义了可以访问的本地描述符的数量;
    • 任务状态段可用于硬件管理的上下文切换;
    • 受控的呼叫门,可以允许程序调用操作系统 - 但只能通过精心管理的入口点。
  • 属性
    在相关的情况下,还维护了段的某些属性:
    • 只读与读写;
    • Segment 当前是否存在 - 允许按需内存管理;
    • 什么级别的代码(操作系统与驱动程序与程序)可以访问此段。

真正的保护终于!

如果操作系统将段描述表保留在仅由程序无法访问的段中,那么它可以严格管理定义了哪些段,以及为每个段分配和访问的内存。程序可以制造它喜欢的任何段寄存器值 - 但是如果它具有将其实际加载段寄存器中大胆性 !…… CPU 硬件将认识到所提出的描述符值破坏了大量规则中的任何一个,并且它不会完成请求,而是会向操作系统引发异常,以允许它处理错误的程序。 ** **

这个例外通常是#13,一般保护例外 - 以微软 Windows 闻名世界……(任何人都认为英特尔工程师迷信?)

错误

可能发生的各种错误包括:

  • 如果建议的描述符索引大于表的大小;

  • 如果提议的描述符是系统描述符而不是代码,数据或堆栈;

  • 如果提议的描述符比请求程序更具特权;

  • 如果建议的描述符被标记为不可读(例如代码段),但它被尝试为 Read 而不是 Executed;

  • 如果提议的描述符标记为不存在。

    请注意,最后一个可能不是程序的致命问题:操作系统可以记下该标志,恢复 Segment,将其标记为现在然后允许故障指令继续成功。

或者,也许描述符已成功加载到段寄存器中,但随后使用它进行访问会破坏许多规则之一:

  • 段寄存器加载了 GDT 的 0x0000 描述符索引。这被硬件保留为 NULL;
  • 如果加载的描述符标记为只读,但尝试写入它。
  • 如果访问的任何部分(1,2,4 或更多字节)超出了段的限制。