x86实模式中的段大小

Ark*_*rma 5 x86 real-mode memory-segmentation

我对实际模式中的段大小有一个疑问,因为它们不能超过64K但可能小于 64K .我的问题是如何初始化这些段大小和基址?就像GDT和LDT处于保护模式一样.实模式段也可以重叠,不相交或相邻.像BIOS有一些保留区域用于特定的事情,如启动代码,视频缓冲区等装配程序需要做类似的事情吗?

Pet*_*des 7

实模式下的段限制为 64K,即使在 386 或更高版本的 CPU 上,您可以通过前缀使用 32 位地址大小。例如mov ax, [edx + ecx*4]在实模式下仍然限制为 64 KiB 的偏移。

如果超过此限制,则会在 286+ 上引发 #GP 异常。(或者#SS如果该段是 SS)。
8086没有#SS或#GP例外,它没有一般保护或其他保护,只是使用Sreg << 4添加到偏移量来形成线性地址。

16 位地址大小可以通过字或更宽的访问超过 64K 段限制seg:FFFF。在 8086 上,高字节来自seg:0000(在为第二个内存总线事务计算新的线性地址之前,对逻辑地址中的偏移量进行包装,而不是访问该段的 64K 线性范围之外)。
在 286 及更高版本上,#GP或者#SS在这种情况下也适用于数据和指令。 https://www.os2museum.com/wp/does-eip-wrap-around-in-16-bit-segments/

一般来说,寻址模式如[bx + si + 1]16 位换行。(push word当 SP=0 时,包装到 SP=FFFEh,只要堆栈对齐就没有问题)。因此,只有使用0x67地址大小前缀(在 386 中添加)进行寻址模式的代码[eax]才能超出实模式中的段限制,段末尾的字或更宽的访问除外。

在 8086 上,在最高可能地址的 64K 范围内开始的段在 1MiB 处环绕,如果禁用 A20,则在更高版本的 CPU 上。FFFF:FFFF否则,对于像seg:off = 0x10ffefLinear这样的地址,它们会扩展超过 1MiB 。请参阅什么是段以及如何在 8086 模式下对它们进行寻址?


虚幻模式:386实模式的平面内存模型

如果切换到保护模式并设置段寄存器,CPU 会在内部缓存段描述(基址 + 限制),即使切换回 16 位实模式也是如此。这种情况称为虚幻模式

在 16 位模式下写入段寄存器仅将段基址设置为 ,value << 4而不更改限制,因此虚幻模式对于 CS 以外的段来说有些持久。CS:EIP 很特殊,特别是当您需要避免在从中断或其他情况返回时将 EIP 截断为 16 位时。请参阅前面链接的 osdev wiki。

push///使用或根据当前堆栈段描述符中的标志pop;地址大小前缀仅影响诸如vs.之类的内容。callretSS:ESPSS:SPBpush 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 上做类似的事情。

  • 不太正确。如果您处于“虚幻模式”并且修改了段寄存器,描述符缓存基址将相应更改,但描述符缓存限制将保持不变。虚幻模式应保持不变,直到您下次切换到保护模式并更改相关段寄存器的段限制和基址为止。 (3认同)
  • 还有另一种机制可以在实模式(包括虚幻模式)下更改它们,即通过 LOADALL 指令,但该指令在大多数处理器上不可用。LOADALL 指令在 Intel 386 和 286 上很有用,因为您可以有效地获得虚幻模式,而无需切换到保护模式。在 286 上,这是一个额外的好处,因为从保护模式切换回实模式会产生很高的性能成本。 (2认同)
  • 郑重声明,在 80 年代末 90 年代初有一些不寻常的 BIOS,当使用某些 BIOS 中断(驱动器访问等)时,它们会悄悄切换到保护模式(可能会重置虚幻模式)。 (2认同)
  • @HadiBrais关于SMM和Int 6h,需要阅读RSM。它具有类似于 LOADALL 的执行状态,并且 SMM 可以在返回到之前的 CPU 模式之前修改状态,从而模拟大部分 LOADALL 的效果,而无需将 int 6h 更改为保护模式(以设置虚幻模式):https:// /asm.inightmare.org/opcodelst/index.php?op=RSM (2认同)

nio*_*nio 3

在实模式下,分段地址被硬连线到内存中。要获得物理地址,您可以使用以下等式:

physical address = segment * 16 + offset
Run Code Online (Sandbox Code Playgroud)

段地址和偏移地址都是 16 位。通过使用这个方程,您可以毫无问题地创建一个 20 位地址并访问低 640kB 的 RAM。

没有任何表可以保存某个段所在的位置。问题是您必须设置段寄存器和偏移寄存器才能访问任何地址。因此,您可以通过一个简单的循环来访问最多 64k 的 RAM 字节,该循环仅递增偏移寄存器,这使得对更大缓冲区的内存访问不如平面模型那么舒服。

  • @nio:如果将对象排列为段落对齐,则只需加载段寄存器即可访问存储在对象内已知偏移量处的内容。我不知道有任何编译语言可以利用这一点,但这是汇编代码中的常见技巧。 (2认同)