保护模式
介绍
当 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 或更多字节)超出了段的限制。