为什么使用非完全序列化指令时,用`mov CR0, ...`改变`PG`后需要跳转一次?

问题描述 投票:0回答:2

英特尔® 64 和 IA-32 架构软件开发人员手册第 3A 卷 9.3 序列化指令中

当执行启用或禁用分页的指令时(即更改控制寄存器中的PG标志) CR0),该指令应该后跟跳转指令。跳转指令的目标指令是 使用 PG 标志的新设置(即启用或禁用分页)来获取,但跳转指令 其本身是使用之前的设置获取的。 Pentium 4、Intel Xeon 和 P6 系列处理器不需要 移动到寄存器 CR0 之后的跳转操作(因为在 Pentium 4 中使用 MOV 指令, Intel Xeon 或 P6 系列处理器写入 CR0 是完全序列化)。

“序列化指令”将在运行之前“序列化指令执行流”以避免重新排序。

问:

  1. 与分页相关的特殊序列化指令之后的“跳转指令”的目的是什么(即

    mov

    一个CR0
    寄存器操作数
    )?是否意味着刷新页表或其他?

  2. 完全序列化”意味着什么,因此它不需要遵循“序列化指令”的跳转?

assembly x86 paging virtual-memory osdev
2个回答
1
投票
“完全序列化”意味着代码预取缓冲区/管道阶段被丢弃,因此这些指令字节的代码获取将使用 CS:EIP 的新解释重新完成(作为要由页表转换的虚拟地址),而不是使用旧的线性=物理解释来执行已获取的指令。 (或者如果禁用分页,则相反,旧的是线性=虚拟。)

如果没有跳转,在

mov cr0, reg

 是非序列化的旧 CPU 上,执行后续指令 
可以 使用使用旧的 CS:EIP 解释的代码获取结果。

(即使在新的 CPU 上,对于像

if (new_code_written_by_another_core) jmp new_code

 这样的情况,实际上也需要序列化指令 - 由于 LoadLoad 和 StoreStore 排序,释放/获取语义在 x86 上免费发生,但是代码获取与数据加载是分开的,并且可以获取您要跳转到的内存位置中的旧值。也许这有助于理解完全序列化的意义。)


通常,启用或禁用分页的代码位于身份映射页面(虚拟=物理地址)中,因此无论您是否立即跳转,都应该没有多大关系(如果有的话?)。除非您的代码位于该页面的末尾附近,并且下一页没有进行身份映射。或者,除非与您稍后可能运行的其他机器状态更改指令进行交互,否则除“陈旧”代码获取之外的其他内容都是相关的。

立即跳转似乎是一种合理的防御性编码实践(在 P6 / P4 之前的 CPU 上),可以确保在页表错误时立即出现页面错误,而不会在预取缓冲区到达之后的某个时刻出现混乱。从虚拟 CS:EIP 获取的指令。

英特尔手册对于此类内容的推荐代码序列往往比较保守。例如从实模式更改为长模式的序列涉及暂时进入保护模式,因此人们不愿意假设未来的 CPU 将以相同的方式工作,并采用直接从实模式进入的较短序列。

https://wiki.osdev.org/Setting_Up_Long_Mode

在这种情况下,我怀疑这只是最简单和最简单的沟通方式,如果你的页表错误,你会在获取跳转目标时出现页面错误,而不是

mov cr0, eax

之后的指令,除非你'重新安装较新的 CPU。因为软件错误确实会发生,并且很难理解它们,尤其是在过去的糟糕日子里,当时更常见的是仅在真实硬件上进行测试,而不是在模拟器中进行测试,在模拟器中您可以单步执行并让它告诉您为什么即使您正在切换模式并且没有使用任何崩溃报告双故障处理程序设置 IVT 或 IDT,系统也会崩溃。


0
投票
取指令实际上就是读取内存,把数据当作代码。

如果您继续获取 PG=0 的指令,则意味着代码不会经过翻译,IP 将继续“扁平”(不翻译)。

在jmp/序列化指令之后,指令将转到新路径并使用“新”翻译作为从中获取的正确代码。

© www.soinside.com 2019 - 2024. All rights reserved.