Segmentation
实模式
在 实模式 中,你使用A:B形式的逻辑地址来寻址内存。 使用等式将其转换为物理地址:
物理地址 = (A * 0x10) B
纯实模式的寄存器被限制为16位以进行寻址。 16位元可以表示0到64k之间的任何整数。 这意味着,如果我们将A设置为固定值并允许B更改,则可以解析64k的内存区域。 这个64k的区域称为段。
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中包含的单词写入地址es:di
CMPSB将在DS:SI处的字节与在ES:DI处的字节进行比较,如果它们相等,则设置零标志,并根据方向标志的状态递减/递增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 Call完全相同,但是CS/IP的当前值在新位置执行之前被推到 stack 上。
INT
处理器从中断向量表中读取CS/IP的新值,然后在将EFLAGS推送到 堆栈 后执行有效的far调用。
Far Return
在这里,处理器将 堆栈 的返回段/偏移量弹出到CS/IP中,并将执行切换到该地址。
IRET
除了CS/IP之外,这与处理器从 stack 弹出EFLAGS的远返回完全相同。
除了这些情况之外,没有任何指令会改变CS的值。
保护模式
- CPU制造商和大多数程序员都认为分段是受保护模式下过时的内存保护技术。 在长模式下不再支持。 这里的信息是获得保护模式工作所必需的; 同样需要64位GDT进入长模式,段仍然用于从长模式跳到兼容模式,反之。 如果你想认真对待OS开发,我们强烈建议使用平面内存模型和 paging 作为内存管理技术。 有关更多信息,请咨询 x86-64。
- 阅读更多关于 Global Descriptor Table
在 保护模式 中,你使用表格a: B中的逻辑地址来寻址内存。 与 Real Mode 一样,A是段部分,B是该段内的偏移量。 处于保护模式的寄存器被限制为32位。 32位可以表示0到4 GiB之间的任何整数。
因为B可以是0到4GiB之间的任何值,所以我们的细分现在最大大小为4 GiB (与实际模式相同的推理)。
现在了解区别。
在保护模式下,A不是段的绝对值。 在保护模式A是一个选择器。 选择器表示一个被称为 全局描述符表 (GDT) 的系统表的偏移量。GDT包含描述符列表。 这些描述符中的每一个都包含描述段特征的信息。
每个段描述符包含以下信息:
- 段的基地址
- 段中的默认操作大小 (16位/32位)
- 描述符的权限级别 (Ring0 -> Ring3)
- 粒度 (段限制以字节/4kb为单位)
- 分段限制 (分段内的最大合法偏移量)
- 段存在 (是否存在)
- 描述符类型 (0 = 系统; 1 = 代码/数据)
- 段类型 (Code/Data/Read/Write/Accessed/Conforming/Non-Conforming/Expand-Up/Expand-Down)
出于此解释的目的,我只对3件事感兴趣。 基址、限制和描述符类型。
如果描述符类型是明确的 (系统类型),那么描述符实际上并不是在描述段,而是在描述一种特殊的门机制,在哪里可以找到LDT或TSS。 这些与一般寻址无关,所以我假设描述符类型为1 (代码/数据类型),其余部分请阅读英特尔手册。
段由其基址和限制来描述。 还记得在实模式下,该段是内存中的64k区域吗? 这里唯一的区别是段的大小不是固定的。 描述符提供的基地址是段的开始,限制是处理器在产生异常之前允许的最大偏移量。
所以我们的保护模式段中的物理地址范围是:
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个描述符 (通常为0和3 Ring),一个用于代码,一个用于数据,它们都精确地描述了同一段。 唯一的区别是代码描述符被加载到CS中,并且数据描述符被所有其他段寄存器使用。 你既需要Code又需要Data描述符的原因是,处理器将不允许你使用数据描述符加载CS (这是为了在使用分段内存模型时帮助安全性,尽管在flat-memory模型中没有用,但仍然需要它,因为你无法关闭分段)。
- 一般来说,如果你想使用分段机制,通过让不同的段寄存器表示具有不同基址的段,你将无法使用现代C编译器,并且很可能仅限于汇编。
- 因此,如果你要使用C,请执行C世界的其余部分所做的操作,即设置一个平面内存模型,使用分页,并忽略细分甚至存在的事实。
关于Pascal [FPC]
以上可能在理论上适用于FreePascal,但是,实际上,如果编译器完全注意相同的话,则忽略了。 使用代码和数据的双段,因此如上所述,需要。但是,要遵守大小限制。(长度不必为4gb)
“* 通常,如果要使用分段机制,则通过使不同的段寄存器表示具有不同基址的段,你将无法使用现代C编译器,并且很可能仅限于汇编。”
Freepascal根本不是这样。
A:B中的 “A” 是允许48和64位指针引用的内容,不仅使用Pascal的NewFrontier单元,而且还使用FreePascal (单词: Longint指针引用)。
- 假设代码和数据占用相同的空间 (至少使用PAE NX位和未使用的分页单元) 首先允许流氓/病毒之类的代码来利用机器。 英特尔规格甚至这样说。代码和数据必须分开。 尽管即使在最新的操作系统中也启用了NX位,但微软仍然困扰着这个问题。
另见
讨论帖