我对实模式下段的大小有一个疑问,因为它们不能超过 64K,但可以小于 64K。我的问题是这些段大小和基地址是如何初始化的?就像在保护模式下有 GDT 和 LDT。实模式段也可以重叠、不相交或相邻。就像 BIOS 为特定的东西(如引导代码、视频缓冲区等)保留了一些区域一样,汇编程序是否需要做类似的事情?
2 回答
在实模式下,分段地址被硬连线到内存中。要获得物理地址,您可以使用以下等式:
physical address = segment * 16 + offset
段地址和偏移地址都是 16 位的。通过使用这个等式,您可以创建一个 20 位地址并毫无问题地访问低 640kB 的 RAM。
没有表可以保存某些段所在的位置。问题是您必须同时设置段寄存器和偏移寄存器才能访问任何地址。因此,您可以通过一个简单的循环访问最多 64k 的 RAM 字节,该循环只增加偏移寄存器,这使得对较大缓冲区的内存访问不如平面模型舒适。
实模式下的段限制为 64k,即使在 386 或更高版本的 CPU 上,您可以通过 prefixes 使用 32 位地址大小。例如mov ax, [edx + ecx*4]
,在实模式下仍限制为 64 kiB 的偏移量。
如果超出此限制,则会引发#GP 异常。(或者#SS
如果该段是 SS)。
16 位地址大小不能超过 64k 段限制,因为寻址模式如[bx + si]
换行为 16 位。因此,只有0x67
在实模式下使用地址大小前缀(在 386 中添加)的代码才会遇到段限制。8086 不必检查限制,只需添加Sreg << 4
寻址模式的偏移量,使限制隐含 64k。
在最高可能地址的 64k 内开始的段在 8086 上以 1MiB 环绕,如果 A20 被禁用,则在更高版本的 CPU 上环绕。FFFF:FFFF
否则,对于像seg:off =0x10ffef
线性这样的地址,它们会超过 1MiB 。请参阅什么是段以及如何在 8086 模式下对其进行寻址?
如果切换到保护模式并设置段寄存器,CPU 会在内部缓存段描述(基址 + 限制),即使切换回 16 位实模式也是如此。这种情况称为虚幻模式。
在 16 位模式下写入段寄存器只会将段基数设置为value << 4
而不更改限制,因此unreal
模式对于 CS 以外的段有些持久。CS:EIP 是特殊的,特别是如果您需要避免在从中断或其他情况返回时将 EIP 截断为 16 位。请参阅该 osdev wiki 链接。
push
///根据当前栈段描述符中pop
的标志使用or call
;地址大小前缀仅影响vs.之类的东西。ret
SS:ESP
SS:SP
B
push word [eax]
push word [si]
当您在实模式下将值写入段寄存器时,GDT / LDT 将被忽略。该值直接用于设置缓存的段基数,而不是作为选择器。
(每个段都是独立的;虚幻模式不是像受保护与真实那样的实际模式;CPU 处于实模式。例如,写入 FS 寄存器会使该段恢复正常的实模式行为,但不会更改其他。它只是在实模式下使用具有更大限制的缓存段描述符的名称,因此您可以将 32 位地址大小用于更大的平面地址空间。通常使用 base=0 和 limit=4G)
AFAIK,没有办法在实模式下查询段的内部限制值。 lsl
直接从内存中 GDT / LDT 中的描述符加载段限制值,而不是从内部值(所以它不是你想要的),并且无论如何它在实模式下都不可用。
有关有意或无意地将片段从虚幻模式中取出的更多详细信息,请参阅对此答案的评论。
286 和 386 CPU 支持可以从实模式设置段限制的LOADALL
指令,但后来的 CPU 没有它。评论者说 SMM(系统管理模式)可能能够在现代 x86 上做类似的事情。