要求澄清“段寄存器继续指向与实地址模式相同的线性地址”

问题描述

问题是关于在英特尔 i386 上从实模式切换到保护模式时代码段选择器的持久有效性。切换代码如下(摘自xv6 x86版本的bootasm.S):

9138 # Switch from real to protected mode. Use a bootstrap GDT that makes
9139 # virtual addresses map directly to physical addresses so that the
9140 # effective memory map doesn’t change during the transition.
9141 lgdt gdtdesc
9142 movl %cr0,%eax
9143 orl $CR0_PE,%eax
9144 movl %eax,%cr0
9150 # Complete the transition to 32−bit protected mode by using a long jmp
9151 # to reload %cs and %eip. The segment descriptors are set up with no
9152 # translation,so that the mapping is still the identity mapping.
9153 ljmp $(SEG_KCODE<<3),$start32

GDT 布局如下:

9182 gdt:
9183 SEG_NULLASM # null seg
9184 SEG_ASM(STA_X|STA_R,0x0,0xffffffff) # code seg
9185 SEG_ASM(STA_W,0xffffffff) # data seg

在执行第 9144 行后,处理器切换到保护模式,在该模式中启用了仅段内存管理(但尚未启用分页)。我的理解是,由于segment MM已经使能了,接下来的指令的取指应该符合segment MM的规则。然而,此时(紧接在第 9153 行之前),代码选择器仍为 0,在我的理解中,这意味着代码段应该选择 GDT 中的第零个描述符,该描述符为空。但是我的问题自然而然地出现了,这样的空描述符如何加载假设的 ljmp 指令?我试图通过谷歌搜索来回答我的问题,一份文件给出了一些解释如下:http://www.logix.cz/michal/doc/i386/chp10-03.htm#10-03

段寄存器继续指向相同的线性地址 如实地址模式

这句话似乎回答了我的问题:如果段寄存器继续指向相同的线性地址,那么下一条指令应该和实模式下的一样,即ljmp。但是我马上就有了一系列新问题:为什么段选择器可以“继续指向相同的线性地址”?处理器不是已更改为保护模式吗? %cs 中的 0 值不是指向第零个描述符,而不是第一个(在第 9184 行中设置的),它是获取 ljmp 指令的假定描述符吗? x86 CPU 如何神奇地知道 ljmp 是它应该执行的下一条指令?描述这种魔法的任何手册中的描述在哪里?我试图说服自己 ljmp 已在处理器的指令队列中预取,但同一网页的第二段告诉我预取的 ljmp(如果有)已失效,因此 CPU 应该重新获取下一条指令。您能否解释一下“段寄存器如何神奇地继续指向与实地址模式中相同的线性地址”?谢谢。

PS,我正在使用的 CPU 兼容 intel i386。

解决方法

现代参考文献是 Intel Software Developer's Manual,第 3A 卷,第 9.9.1 节,“切换到保护模式”。

英特尔并不热衷于解释魔法在内部是如何运作的。它说的是,您需要知道的是,如果您的 movl %eax,%cr0 紧随其后是远跳或远呼叫,那么一切都会正常进行。如果您将任何其他指令放在那里,那么“可能会发生随机故障”(他们的措辞)。

正如它所说,%cs 继续保持其先前的值,并且如果您在 movl %eax,%cr0 之后执行远调用作为指令,那么这可能是将被压入堆栈的值。 (堆栈在哪里是另一个有趣的问题 - 我认为每个人都使用跳转,所以它很少出现。)但是对于这条指令,它显然没有以通常的方式用作选择器。

关于它如何工作的猜测:我们知道在保护模式下,有隐藏的寄存器存储段属性,并在加载段寄存器时从描述符表中重新加载。所以 movl %eax,%cr0 可能会导致对应于 %cs 的隐藏寄存器加载一个段的属性,该段的基地址是当前 16 位段的线性地址:例如如果 %cs 包含 0x1234,则它可能是基地址为 0x12340 的段。但是 %cs 寄存器本身可以单独放置,暂时不匹配其隐藏的对应项。然后,如果 %eip 的高位为零,则将从正确的位置获取下一条指令。该指令必须是将重新加载 %cs 以及隐藏段属性寄存器的长跳转。

也有可能它只是设置了一些内部标志,表示“即使在保护模式下,根据实模式地址转换获取下一条指令”。然后,当发生远跳转时,或在获取一条指令后或类似情况时,此标志会被清除。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...