要求澄清 "the segment registers continue to point to the same linear addresses as in real address mode"

Ask for clarification about "the segment registers continue to point to the same linear addresses as in real address mode"

问题是关于在英特尔 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, 0x0, 0xffffffff) # data seg

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

The segment registers continue to point to the same linear addresses as in real address mode

这句话似乎回答了我的问题:如果段寄存器继续指向相同的线性地址,那么下一条指令应该和实模式下一样,即ljmp。但我立即有了一系列新问题:为什么段选择器可以“继续指向相同的线性地址”?处理器不是改成保护模式了吗? %cs 中的 0 值不是指向第 0 个描述符,而不是第 1 个(在第 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 之后的指令执行远调用,大概就是将被压入堆栈的值。 (堆栈在哪里是另一个有趣的问题——我认为每个人都使用跳转,所以它很少出现。)但是对于这条指令,它显然没有以通常的方式用作选择器。

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

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