临时小驻

求仁得仁,复无怨怼。

IA32(x86) 内存管理:分段机制

2017-10-16 20:41:00 +0800

IA32(x86) CPU 架构在 8086 到 80386 的发展中,为了兼容旧程序,又提供新特性,引入了实模式和保护模式的概念。CPU 首先从实模式启动,这可以认为是 8086 的运行模式,并由代码控制切换到保护模式,获得新特性。

与 IA32 有关的硬件编程,需要参考 IA32 架构软件开发人员手册


分段是一种隔离不同的代码、数据、栈模块的机制,能够保证不同进程或任务不会互相干扰。我们可以为一个进程分配属于它的段集合,CPU 的硬件机制会保证其代码不会越权访问段,也不会访问到段外的地址。

保护模式下的寻址不再由段寄存器和偏移量寄存器的值计算得到,必须借助于段。分段机制是保护模式下必须开启的,没有标志位可用于关闭分段机制。

X86 CPU 的可寻址空间(即线性地址空间)为 0 - 0xFFFFFFFFH。我们将线性地址空间分为较小的、受保护的地址空间,并命名为段。

逻辑地址由段选择子和偏移量组成。 段选择子是一个段的唯一标识,能够找到对应的段描述符。 段描述符描述了段的大小、访问权限、优先级、类型及段基址。 线性地址由段基址和偏移量组成。 通过向 CPU 传入逻辑地址,CPU 能够找到对应的段,将逻辑地址映射到线性地址。如果没有开启分页机制,则线性地址即为物理地址。

段选择子 Selector

段选择子 16 位。它不直接指向段或段描述符,而是借助于 GDT/LDT。

段选择子 Selector

  • RPL 请求特权级。指明以哪一特权级访问段。
  • TI 表指示标记。指明使用 0(GDT) 还是 1(LDT) 查找段描述符。
  • INDEX 索引。13 位(8192)。目标描述符在 GDT/LDT 中的 INDEX * 8(段描述符长度) 处。

通过段选择子,能够唯一确定一个段。

段描述符 Descriptor

段描述符 64 位。它是包含段的元信息的数据结构。

段描述符 Segment Descriptor

一般地,段描述符由编译器、连接器、装载器或操作系统设立。

  • 段基址、段界限均是两段比特位拼接而成。
  • S 系统描述符标志位。指明是系统描述符(1)或普通代码段或数据段描述符(0)。
  • TYPE 类型域。和 S 一起指明段的类型,并确定段的访问权限和增长方向。
  • DPL 描述符特权级。指明段的特权级。
  • P 段存在标志位。指明段当前是(1)否(0)在内存中。
  • AVL 由用户(操作系统)使用。
  • D/B 默认操作数大小标志位。指明是 32 位的段(1)还是 16 位(0)。
  • G 粒度标志位。指明段界限的单位是 1B(0)还是 4KB(1)。段基址的粒度永远是 1 字节。

段描述符类型

S == 0 时

S == 0 时,TYPE 含义如下表所示。

代码段和数据段类型

最高位决定是数据段(0)还是代码段(1)。

A 访问位。表示最后一次清零后,段是否被访问过。CPU 会在把段选择子装入段寄存器后,将段的访问位置 1,并保持到操作系统进行显式清零为止。访问位一般用于虚拟内存管理和调试。

对于数据段:

W 是否可写。 E 扩展方向。E == 0 时 [BASE, BASE + limit] 这段空间是可访问的,其它空间不可访问,称为向上扩展;E == 1 时 (BASE + limit, MAX] 不可访问,其它空间可访问,称为向下扩展。 D/B 称为 B 标志位。同时指明上述的 MAX 是 4GB(1) 还是 64KB(0)。

堆栈段必须是可读写的数据段。为 SS 装载一个不可写的数据段选择子会导致“一般保护异常”。

对于代码段:

R 是否可读。表示是否可以从代码段里读取数据。 C 是否有一致性。

进程转入有更高特权级的有一致性的代码段可以继续运行。一般地,不能被更低特权级的进程访问的代码应该被载入非一致性代码段。

所有的数据段都是非一致性的,但无需使用特别的访问门,即可被更高特权级的进程访问。

保护模式中,代码段是不可写的。

S == 1

S == 1 时,TYPE 含义如下表所示。

系统段和门描述符类型

这里不详细描述系统描述符。

段描述符表

段描述符表是一个以段描述符为元素的数组。段描述符表的长度是可变的,最多可以有 $2^13 = 8192$ 个段描述符,每个段描述符 8 字节(64 位),即 64KB。

有两种描述符表:全局描述符表(GDT)和局部描述符表(LDT)。

在进入保护模式前,必须定义一个 GDT,以供所有进程或任务使用。 GDT 的线性基址和界限需装入 GDTR 寄存器。

GDTR

基址以 8 字节方式对齐可获得最好的处理器性能。 界限以字节计。因为段描述符总是 8 字节长,所以段描述符表的界限应该总是 8N-1

LDT 位于类型为 LDT 的系统段内。GDT 必须包含至少一个指向 LDT 段的段描述符。 LDT 的段选择子需装入 LDTR 寄存器。

LDTR

GDT 的第 0 项是不用的,称为空段选择子。空选择子一般用于初始化未使用的段寄存器。 CS 或 SS 装载空选择子会产生“一般保护异常”;其他段寄存器指向空选择子时不会产生异常,但使用空选择子访问内存会产生异常。

内部寻址过程

提供逻辑地址,得知段选择子,假设是 GDT 段,通过 GDTR 找到 GDT,通过段选择子和 GDT 找到段描述符,通过段描述符得知段基址,即可将逻辑地址转换为线性地址。

分段内存模型

用不同的方法使用分段机制,能够建立不同的分段内存模型。

平坦(flat)模型中,程序访问一个连续的、没有分段的地址空间。

基本平坦模型可以通过建立 2 个段描述符,分别指向代码段和数据段来实现。这两个段被映射到整个线性地址空间——这句话的意思是,两个段的基址为 0,界限为 4GB。 这样,即使访问的地址超过了物理内存,CPU 也不会抛出“超出内存范围”的异常。

保护平坦模型的段界限设为在实际的物理内存范围内,如果超出则产生“一般保护异常(#GP)”。该模型最小程度地利用了硬件的保护机制来防止程序错误。

更复杂地,定义 4 个段描述符,分别指向特权级 0 和 3 的代码段和数据段。一般地,这些段互相重叠,都从地址 0 开始。这种平坦分段模型,再加上简单的分页结构,已经能够保护操作系统免受应用程序干扰。如果再为每个进程分配一个分页结构,也能够保护应用程序。

多段模型充分利用分段机制,在各种场景中使用硬件强制保护机制。访问内存时,通过每一个段寄存器得到所使用的段描述符,提供相应权限,进行地址转换。

原文链接 https://blog.xupu.name//p/2017-10-x86-memory-management-segmentation/

如无特别指明,本站原创文章均采用 CC BY-NC-ND 4.0 许可,转载或引用请注明出处,更多许可信息请查阅这里