关键点是在现代 CPU 中动词执行并不意味着你认为的意思。
执行指令是计算其输出和副作用(如果有)的行为。
然而,这没有改变程序状态。
乍一看这似乎很难理解,但实际上并不奇怪。
CPU有一个相当大的内存,由它的所有寄存器组成,大部分内存是not对于程序员可见,这部分被称为建筑状态.
架构状态 (AS) 是 CPU 手册中记录的内容以及由指令序列(例如程序)更改的内容。
由于更改 AS 只能通过 ISA(手册)中给出的语义进行,并且 ISA 指定串行语义(指令按照程序顺序一个接一个地完成),因此不允许并行性。
然而,现代 CPU 拥有大量可以独立完成工作的资源(称为执行单元)。
为了利用所有这些资源,CPU 的前端(负责从内存层次结构读取指令并将其馈送到执行单元的部分)能够在每个周期到达、解码和输出多于一条指令。
前端和后端(执行单元所在的位置)之间的边界不再真正处理指令(而是处理微指令),但这是 x86 CISC 的麻烦。
所以现在 CPU 一次被给予 4/6 uops 来“执行”,但如果 ISA 是串行的,除了对这些 uops 进行排队之外,它还能做什么?
好吧,前端的设计是为了让这些微指令不在AS上运行,而是在a上运行影子状态(SS,我在这里的术语),它们的操作数被重命名,由CPU的大隐形内存的一部分组成。
改变并行或乱序都可以,因为它不是 AS。
这就是执行:改变 SS。
真的值得吗?毕竟,AS 才是最重要的。
嗯,与执行相比,将 SS 转移到 AS 确实很快,所以这是值得的。
这是一个“重命名回来”(反转以前的重命名)的问题,它被称为retiring的说明。
事实上,退休的意义远不止于此。
由于执行不会影响 AS,因此副作用也不会影响它。
这些副作用包括异常,但是推测性地处理异常太麻烦(需要协调很多资源),所以异常处理是delayed直到退休。
这还有一个优点,就是在处理异常时拥有正确的 AS,并且只有在实际必须发生异常时才引发异常。
推测执行的要点是bet,CPU 打赌指令序列不会生成任何异常(包括页面错误),因此在大多数检查关闭的情况下执行它(我不能排除,无论如何,某些检查都不会进行),从而获得很多优势。
当需要废弃这些指令时,将检查赌注,如果任何失败,则 SS 将被丢弃。
这就是为什么推测执行不会使程序崩溃。
Spectre 所依赖的事实是,推测性执行确实在某种意义上改变了 AS:缓存不会失效(再次出于性能原因,当赌注关闭时,SS 不会被复制到 AS 中)并且可能发生定时攻击。
这可以通过多种方式纠正,包括在从 TLB 读取时执行基本权限检查(毕竟仅使用权限 0 和 3,因此逻辑很简单)或向缓存行添加一位以将其标记为推测的(被非推测代码视为无效)。