Ark*_*rma 5 x86 real-mode memory-segmentation
我对实际模式中的段大小有一个疑问,因为它们不能超过64K但可能小于 64K .我的问题是如何初始化这些段大小和基址?就像GDT和LDT处于保护模式一样.实模式段也可以重叠,不相交或相邻.像BIOS有一些保留区域用于特定的事情,如启动代码,视频缓冲区等装配程序需要做类似的事情吗?
实模式下的段限制为 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 模式下对它们进行寻址?
如果切换到保护模式并设置段寄存器,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 上做类似的事情。
在实模式下,分段地址被硬连线到内存中。要获得物理地址,您可以使用以下等式:
physical address = segment * 16 + offset
Run Code Online (Sandbox Code Playgroud)
段地址和偏移地址都是 16 位。通过使用这个方程,您可以毫无问题地创建一个 20 位地址并访问低 640kB 的 RAM。
没有任何表可以保存某个段所在的位置。问题是您必须设置段寄存器和偏移寄存器才能访问任何地址。因此,您可以通过一个简单的循环来访问最多 64k 的 RAM 字节,该循环仅递增偏移寄存器,这使得对更大缓冲区的内存访问不如平面模型那么舒服。