是的,它们确实需要机器代码和所有内存操作数。
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(每个单独的来宾内核页面表子树):
movsq
或movsw
跨页边界的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缓冲区。通过将计数器的掩码配置为仅对用户空间指令而不是内核进行计数,很可能是每次返回用户空间时,它一直试图使计数器溢出并将样本存储在缓冲区中,从而产生页面错误。