x86 指令是否需要它们自己的编码以及它们的所有参数同时存在于内存中?

Do x86 instructions require their own encoding as well as all of their arguments to be present in memory at the same time?

我正在尝试弄清楚是否可以 运行 一个 Linux 其 RAM 仅由单个物理页面支持的虚拟机。

为了模拟这一点,我修改了 KVM 中的嵌套页面错误处理程序以删除 所有嵌套页面 table (NPT) 条目中的当前位, 除了与当前处理的页面错误对应的那个。

在尝试启动 Linux 客户机时,我观察到使用内存操作数的汇编指令,例如

add [rbp+0x820DDA], ebp

导致页面错误循环,直到我恢复包含该页面的当前位 指令以及操作数中引用的页面(在本例中为 [rbp+0x820DDA])。

我想知道为什么会这样。 CPU 不应该按顺序访问内存页面,即先读取指令然后访问内存操作数吗? 还是 x86 要求指令页和所有操作数页都可以同时访问?

我正在 AMD Zen 1 上测试。

是的,它们确实需要机器代码和所有内存操作数。

Shouldn't the CPU access the memory pages sequentially, i.e. first read the instruction and then access the memory operand?

是的,这在逻辑上是会发生的,但是页面错误异常会中断该两步过程并放弃任何进展。 CPU 无法记住发生页面错误时正在执行的指令。

当页面错误处理程序 returns 在处理有效页面错误后,RIP= 错误指令的地址,因此 CPU 重试执行它 从零开始.

OS 修改错误指令的机器代码并期望它在 iret 之后从页面错误处理程序(或任何其他异常)执行不同的指令是合法的或中断处理程序)。所以 AFAIK 在架构上要求 CPU 在您谈论的情况下从 CS:RIP 重做代码获取。 (假设它甚至对故障 CS:RIP 执行 return 而不是在等待硬页面错误时调度另一个进程,或者在无效页面错误时将 SIGSEGV 传递给信号处理程序。)

它可能也是管理程序 entry/exit 的架构要求。即使在纸面上没有明确禁止,这也不是 CPU 的工作方式。

@torek 评论说 一些 (CISC) 微处理器部分解码指令并在页面错误时转储微寄存器状态,但 x86 不是这样。


一些指令是可中断的,可以取得部分进展,如rep movs (memcpy in a can) 和其他字符串指令,或收集loads/scatter 存储。但唯一的机制是更新架构寄存器,如用于字符串操作的 RCX/RSI/RDI,或用于收集的目标和掩码寄存器(例如 AVX2 vpgatherdd 的手册)。不保留操作码/解码结果在一些隐藏的内部寄存器中,并在页面错误处理程序 iret 后重新启动它。这些指令执行多个单独的数据访问。

另请记住,x86(与大多数 ISA 一样)保证指令是原子的。中断/异常:在中断之前,它们要么完全发生,要么根本不发生。 Interrupting an assembly instruction while it is operating。因此,例如,如果存储部分出现故障,即使没有 lock 前缀,也需要 add [mem], reg 丢弃负载。


访客用户-space 页面的最坏情况数量可能是 6(加上单独的访客内核页面-table 每个子树) :

  • movsqmovsw 跨越页面边界的 2 字节指令,因此解码需要两个页面。
  • qword源操作数[rsi]也是分页
  • qword 目标操作数 [rdi] 也是分页

如果这 6 个页面中有任何一个出错,我们回到原点。

rep movsd也是一个2字节的指令,每一步的进步也是一样的要求。类似 push [mem]pop [mem] 的情况可以用未对齐的堆栈构造。

原因之一(或附带好处)for/of进行收集加载/分散存储"interruptible"(根据进度更新掩码向量)是为了避免增加此最小占用空间以执行单个操作说明。也为了提高一次聚集或分散过程中处理多个故障的效率。


@Brandon 在评论中指出,访客需要其页面 table 在内存中 ,用户-space 页面拆分也可以是 1GiB 拆分,因此两侧位于顶层 PML4 的不同子树中。 HW page walk 需要接触所有这些 guest page-table 页面才能取得进展。这种病态的情况不太可能偶然发生。

允许缓存一些页面table数据,并且不需要重新启动页面遍历从头开始,除非 OS 做了 invlpg 或设置了新的 CR3 顶级页面目录。将页面从不存在更改为存在时,这些都不是必需的;纸上的 x86 保证不需要它(因此 "negative caching" 不存在的 PTE 是不允许的,至少对软件不可见)。因此 CPU 可能不会 VMexit,即使某些访客物理页面 -table 页面实际上并不存在。

PMU 性能计数器 可以启用和配置,这样指令还需要一个性能事件来写入 PEBS 缓冲区那个指令。将计数器的掩码配置为仅计算用户 space 指令,而不是内核,很可能每次您 return 到用户 [时,它都会不断尝试溢出计数器并将样本存储在缓冲区中=98=],产生页面错误。