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


64

我试图弄清楚是否有可能运行仅由单个物理页面支持其RAM的Linux VM。

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

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

add [rbp+0x820DDA], ebp

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

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

我正在AMD Zen 1上进行测试。


2
你为什么想做这个?
SS Anne

11
刚出于技术兴趣:)
savvybug

14
支持有趣的项目构想。
管道

10
这在“在浏览器中的JavaScript中运行的486仿真器上引导Linux”的级别上是疯狂的。我喜欢它。
chrylis-罢工-

3
嘿,显然,我已将这个问题带到了您已经在思考的关于可以保证向前进步的最小工作集的逻辑结论。在您将新的第一段添加到问题之前,我已经回答过。:PI在几个位置添加了一些链接和更多详细信息(例如,允许页面行进器在内部缓存某些来宾页面目录条目),因为由于某种原因使其成为HNQ,这一问题的关注程度超出了我的预期。
Peter Cordes

Answers:


56

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

CPU是否不应该按顺序访问内存页面,即先读取指令然后访问内存操作数?

是的,从逻辑上讲会发生这种情况,但是页面错误异常会中断该两步过程并丢弃任何进度。CPU无法记住发生页面错误时的指令。

当页面错误处理程序在处理了有效的页面错误后返回时,RIP =错误指令的地址,因此CPU 从头开始重试执行它。

操作系统修改错误指令的机器代码并期望它iret在页面错误处理程序(或任何其他异常或中断处理程序)之后执行另一条指令是合法的。因此,对于AFAIK,在架构上要求您在谈论这种情况时,CPU要从CS:RIP重新获取代码。(假设它甚至返回到有问题的CS:RIP,而不是在等待硬页错误的磁盘时安排另一个进程,或者在无效页错误时将SIGSEGV传递给信号处理程序。)

管理程序进入/退出在架构上可能也需要。即使没有在纸上明确禁止使用,也不是CPU的工作方式。

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


一些指令是可中断的,并且可以部分完成,例如rep movs(罐中的memcpy)和其他字符串指令,或者收集负载/分散存储。但是唯一的机制是为字符串操作更新诸如RCX / RSI / RDI之类的体系结构寄存器,或者为收集而更新目标寄存器和掩码寄存器(例如,用于AVX2的vpgatherdd手册)。不保留操作码/解码会导致某些隐藏的内部寄存器,并在从页面错误处理程序中调用iret之后重新启动它。这些是执行多个单独数据访问的指令。

还请记住,x86(与大多数ISA一样)保证指令是原子性的。中断/异常:在中断之前,它们要么完全发生,要么根本不发生。 在运行时中断汇编指令。因此,例如add [mem], reg,如果存储部分发生故障,即使没有lock前缀,也将需要丢弃负载。


当前要取得前进的最坏情况的来宾用户空间页面数可能是6(每个单独的来宾内核页面表子树):

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

如果这6页中的任何一页出现故障,我们将回到第一页。

rep movsd也是2字节的指令,在其中的一个步骤上取得进展将具有相同的要求。类似的情况,例如push [mem]pop [mem]可能以未对齐的堆栈构造。

使聚集负载/分散存储“可中断”(使用其进度更新掩码向量)的原因(或附带好处)之一是避免增加此最小占用空间以执行一条指令。还可以提高一次聚集或分散期间处理多个故障的效率。


@Brandon在注释中指出,guest虚拟机将需要其页表在内存中,并且用户空间页拆分也可以是1GiB拆分,因此,这两个方面位于顶级PML4的不同子树中。硬件分页浏览需要触摸所有这些来宾页表页面以取得进展。这种病理状态不太可能偶然发生。

除非操作系统进行操作或设置新的CR3顶级页面目录,否则允许 TLB(和page-walker内部)缓存某些页面表数据,并且不需要从头开始重新启动page-walk invlpg。将页面从不存在更改为当前时,这些都不是必需的。纸上的x86保证不需要它(因此,不允许对不存在的PTE进行“负缓存”,至少对软件不可见)。因此,即使实际上不存在某些访客物理页表页面,CPU也可能不会VMexit。

可以启用和配置PMU性能计数器,以便该指令还需要一个perf事件,才能将该指令写入PEBS缓冲区。通过将计数器的掩码配置为仅对用户空间指令而不是内核进行计数,很可能是每次返回用户空间时,它一直试图使计数器溢出并将样本存储在缓冲区中,从而产生页面错误。


15
一条指令的最坏情况可能是“ push dword [foo”(甚至只是call [foo])之类的东西,所有内容在“页面目录指针表边界”之间未对齐(总共6页,6页表,6页目录,6个PDPT和1个PML4);启用并配置了CPU的“具有PEBS缓冲区的基于精确事件的采样”功能,以便将push性能监视数据添加到PEBS缓冲区。对于保守的“主持人提供的最少页面,以便来宾可以在病理情况下取得进展”,我希望至少有16页。
布伦丹

4
请注意,这种情况在CISC-y体系结构中一直很常见。一些微处理器在页面错误时部分解码指令并转储微寄存器状态,但是其他微处理器则不和/或要求“ loop-y”指令的地址操作数(m68k上的DBRA,Vax上的MOVC3 / MOVC5等)位于类似的寄存器中。以您的REP MOVS示例为例。
torek

1
@Brendan:有人认为VAX指令的最坏情况约为50页。我忘记了细节,但是您显然会将指令本身放在了页面边界上,对表跨越页面边界使用了诸如翻译表查找之类的东西,对页面边界使用了间接值(rX)[rY],并且以此类推。我认为,最冗长的指令最多占用6个操作数(将它们加载到r0-r5中),所有6个都可能是双间接值。
torek

3
操作系统可以更改指令,但也可以更改EIP。因此,存在一个合理的后续问题。假设采用智能指令补丁方案,最少需要多少页?例如,将未对齐的值复制到对齐的暂存缓冲区,模拟指令,然后将IRET复制到下一条指令。
MSalters

1
包含OS iret指令的页面也需要在内存中。这是一个1字节的指令,因此多了一页。页面错误处理程序的中断地址也需要在内存中,但是可以与上述页面相同。
Stig Hemmer,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.