Segmentation

来自osdev
跳到导航 跳到搜索

实模式

实模式中,你使用A:B形式的逻辑地址来寻址内存。 使用以下方程将其转换为物理地址:

物理地址 = (A * 0x10) + B

纯实模式的寄存器被限制为16位以进行寻址。 16位可以表示0到64k之间的任何整数。 这意味着,如果我们将A设置为固定值并允许B更改,则可以解析64k的内存区域。 这个64k的区域称为一个段(segment)。

A = 64k段,B = 段内的偏移量

段的基址是上面方程的 (A * 0x10) 部分。 很明显,多个段之间可以重叠。

例如,段0x1000的基地址为0x10000。 此段占用物理地址范围0x10000 -> 0x1FFFF,而如果段0x1010的基地址为0x10100。 此段占用物理地址范围0x10100 -> 0x200FF

如你所见,由于段重叠,我们可以使用任一段到达0x10100和0x1FFFF之间的物理地址。

x86系列计算机有6个段寄存器 (CS,DS,ES,FS,GS,SS)。 它们完全独立于彼此。

CS 代码段(Code Segment)
DS 数据段(Data Segment)
SS 段(Stack Segment)
ES 附加段(Extra Segment)
FS 通用目的段(General Purpose Segments)
GS

当你要读取/写入内存时,DS,ES,FS,GS,SS用于形成地址。 它们并不总是必须被显式编码,因为一些处理器操作将隐含使用某些段寄存器。

例如

MOV [SI],AX 语句会将ax中包含的word写入地址DS:SI

MOV ES:[DI],AX 将ax中包含的word写入地址es:di

CMPSB将在DS:SI处的字节与在ES:DI处的字节进行比较,如果它们相等,则设置零标志,并根据方向标志(direction flag)的状态递减/递增SI和DI。

如你所见,通常使用的段寄存器不用包含在指令中,但是实际有使用。 每次你在x86处理器上形成地址时,都会涉及一个段寄存器。


影响段寄存器的操作

除了CS之外,段寄存器还可以从通用寄存器加载(mov ds,ax) 或从堆栈顶部获得 (pop ds)。

CS是唯一不能直接更改的段寄存器。 唯一 (也许还有别的) CS被更改的情况是当代码将执行切换到另一个段时。 唯一可以做到这一点的命令是:

远跳(Far Jump)

这里,在跳转指令中编码CS的新值。 例如,JMP 0x10:0x100说要加载具有段0x10的CS和具有0x100的IP。 CS:IP是要执行指令的逻辑地址。

远调用(Far Call)

这与Far Jump完全相同,但是CS/IP的当前值在新位置执行之前被推到 上。

INT

处理器从中断向量表中读取CS/IP的新值,然后在将EFLAGS推送到 后执行有效的far call。

Far Return

在这里,处理器将 的返回“段/偏移量”弹出到CS/IP中,并将执行切换到该地址。

IRET

除了CS/IP之外,这与处理器从弹出EFLAGS的far return完全相同。

除了这些情况之外,没有任何指令会改变CS的值。

保护模式

CPU制造商和大多数程序员都认为分段是保护模式下已过时的内存保护技术。 在长模式下不再支持。 这里的信息是获得保护模式工作所必需的; 同样需要64位GDT进入长模式,段仍然用于从长模式跳到兼容模式,反之。 如果你想认真对待OS开发,我们强烈建议使用平面内存模型(flat memory model)和 分页 作为内存管理技术。 有关更多信息,请咨询 x86-64
阅读更多关于 Global Descriptor Table

保护模式 中,你也使用形如A:B的逻辑地址来寻址内存。 与 实模式 一样,A是段部分,B是该段内的偏移量。 处于保护模式的寄存器被限制为32位。 32位可以表示0到4 GiB之间的任何整数。

因为B可以是0到4GiB之间的任何值,所以我们的段现在最大大小为4 GiB (因为与实模式分段处理相同的理由)。

现在了解一下两者的区别。

在保护模式下,A不是段的绝对数值。 在保护模式A是一个选择器。 选择器表示一个被称为 全局描述符表 (GDT) 的系统表的偏移量。GDT包含描述符(descriptors)列表。 这些描述符中的每一个都包含描述段特征的信息。

每个段描述符包含以下信息:

  • 段的基地址
  • 段中的默认操作大小 (16位/32位)
  • 描述符的权限级别 (Ring0 -> Ring3)
  • 粒度 (段限制以字节/4kb为单位)
  • 分段限制(limit) (分段内的最大合法偏移量)
  • 段存在 (是否存在)
  • 描述符类型 (0 = 系统; 1 = 代码/数据)
  • 段类型 (Code/Data/Read/Write/Accessed/Conforming/Non-Conforming/Expand-Up/Expand-Down)

出于此处解释的目的,目前只讨论以上的这三部分内容。 基址(base)、限制(limit)和描述符类型。

如果描述符类型是明确的 (系统类型),那么描述符实际上并不是在描述段,而是在描述一种特殊的门机制,说明在哪里可以找到LDT或TSS。 这些与一般寻址无关,所以我假设描述符类型为1 (代码/数据类型),其它详情请阅读英特尔手册。

段由其基址和限制(limit)来描述。 还记得在实模式下,该段是内存中的64k区域吗? 这里唯一的区别是段的大小不是固定的。 描述符提供的基地址是段的开始,限制(limit)是处理器在产生异常之前允许的最大偏移量。

所以我们的保护模式段中的物理地址范围是:

 Segment Base -> Segment Base + Segment Limit

给定一个逻辑地址A: B (记住A是一个选择器),我们可以确定它转换为使用的物理地址:

 Physical address = Segment Base (Found from the descriptor GDT[A]) + B

实模式中的所有其它规则仍然适用。

注意

  • 段可以重叠
  • CS、DS、ES、FS、GS、SS相互独立
  • CS不能直接更改

在保护模式下,CS也可以通过TSS或门进行更改。

关于C语言的备注

  • 大多数C编译器都采用flat-memory模型。
  • 在此模型中,所有段都覆盖了完整的地址空间 (在x86上通常为0->4Gb)。 从本质上讲,这意味着我们完全忽略了A:B逻辑地址的A部分。 这样做的原因是大多数处理器实际上没有分段 (这样编译器优化要容易得多)。
  • 最好要为特权级别留下2个描述符 (通常为Ring 0和Ring 3 ),一个用于代码(Code),一个用于数据(Data),它们都精确地描述了同一段。 唯一的区别是代码描述符被加载到CS中,并且数据描述符被所有其它段寄存器使用。 你既需要Code又需要Data描述符的原因是,处理器将不允许你使用数据描述符加载CS (这是为了在使用分段内存模型时帮助安全性,尽管在flat-memory模型中没有用,但仍然需要它,因为你无法完全不使用分段)。
  • 一般来说,如果你想使用分段机制,通过让不同的段寄存器表示具有不同基址的段,你将无法使用现代C编译器,并且很可能此功能仅限于汇编。
  • 因此,如果你要使用C,请执行C世界的其余部分所做的操作,即设置一个flat-memory模型,使用分页,并需要忽略分段存在的事实。

关于Pascal[FPC]的备注

以上可能在理论上适用于FreePascal,实际上编译器完全忽略了段。 使用代码和数据双段的方式也被使用了,而且如上所述是必要的。但要遵守大小限制。(长度不必为4GB)

前面对于C编译器来说的:“* 一般来说,如果你想使用分段机制,通过让不同的段寄存器表示具有不同基址的段,你将无法使用现代C编译器,并且很可能此功能仅限于汇编。”

这种情况Freepascal根本不是这样。

A:B中的 “A” 是允许48和64位指针引用的内容,不仅使用Pascal的NewFrontier单元,FreePascal也可以 (备注: Longint指针引用)。

  • 假设代码和数据是占用相同的空间 (至少使用PAE NX位和未使用的分页单元)会允许流氓/病毒之类的代码胁持机器。 英特尔规格甚至这样说。代码CODE和数据DATA必须分开。 尽管即使在最新的操作系统中也启用了NX位(译者注:NX指分页中的不可执行标志位),但微软仍然对这个问题模糊不清。(译者注:这里的意思应该是说段类型也有保护机制,分页描述符也有保护机制,所以有点模糊不清)

另见

文章

Segment Limits

论坛主题

外部链接