assembly - 当带有指令的内存被另一个内核更改时,CPU 流水线会发生什么情况?

标签 assembly x86 pipeline cpu-architecture hotpatching

我试图了解 CPU 管道的“获取”阶段如何与内存交互。

假设我有这些说明:

4:  bb 01 00 00 00          mov    $1,%ebx
9:  bb 02 00 00 00          mov    $2,%ebx
e:  b3 03                   mov    $3,%bl

如果 CPU1 将 00 48 c7 c3 04 00 00 00 写入内存地址 8(即 64 位对齐)而 CPU2 正在执行这些相同的指令,会发生什么情况?指令流会自动从 2 条指令变为 1 条指令,如下所示:

4:  bb 01 00 00 00          mov    $1,%ebx
9:  48 c7 c3 04 00 00 00    mov    $4,%rbx

由于 CPU1 写入的内存与 CPU2 读取的内存相同,因此存在争用。 写入会导致 CPU2 流水线在刷新其 L1 缓存时停止吗? 假设 CPU2 刚刚完成了 mov $2 的“获取”pĥase,为了重新获取更新的内存,它会被丢弃吗?

此外,将 2 条指令更改为 1 条指令时还存在原子性问题。

我找到了这个 quite old document 提到“指令获取单元在每个时钟周期从指令缓存存储器中获取一个 32 字节缓存行” 我认为这可以解释为每条指令都从 L1 获取缓存行的新副本,即使它们共享相同的缓存行。 但我不知道这是否/如何适用于现代 CPU。

如果以上是正确的,这意味着在将 mov $2 提取到管道后,下一次提取可能会在地址 e 处获取更新后的值并尝试执行 00 00 (add %al,(%rax)) 这可能会失败。

但是如果 mov $2 的获取将 mov $3 带入“指令缓存”,它会不会 认为下一次获取只是从该缓存中获取指令(并返回 mov $3)而不重新查询 L1 是否有意义? 这将有效地使这 2 条指令的提取成为原子操作,只要它们共享一条高速缓存行即可。

那是什么?基本上有太多未知数,太多我只能推测,所以我非常感谢逐个时钟周期的分割,了解管道的 2 个获取阶段如何与它们访问的内存交互(更改)。

最佳答案

正如 Chris 所说,RFO(Read For Ownership)可以随时使 I-cache 行无效。

根据超标量获取组的排列方式,在 9: 获取 5 字节 mov 之间,但在获取下一条指令之前,缓存行可能会失效在 e:

当获取最终发生时(该核心再次获取缓存行的共享副本),RIP = e 并且它将获取 mov $4,%rbx< 的最后 2 个字节交叉修改代码需要确保没有其他内核在它要编写一条长指令的中间执行。

在这种情况下,您将得到 00 00 add %al, (%rax)

另请注意,写入 CPU 需要确保修改是原子的,例如使用 8 字节存储(Intel P6 和更高版本的 CPU 保证在 1 个缓存行内的任何对齐处存储最多 8 个字节是原子的;AMD 没有),或者 lock cmpxchglock cmpxchg16b。否则,读者可能会看到部分更新的说明。您可以将指令获取视为执行原子 16 字节加载或类似操作。


"The instruction fetch unit fetches one 32-byte cache line in each clock cycle from the instruction cache memory" which I think can be interpreted to mean that each instruction gets a fresh copy of the cache line from L1,

没有。

那个宽取 block 然后被解码成多个 x86 指令! wide fetch 的要点是一次拉入多条指令,而不是每条指令分别重做。该文档似乎是关于 P6 (Pentium III) 的,尽管 P6 一次只执行 16 个字节的实际提取,进入一个 32 字节宽的缓冲区,让 CPU 占用一个 16 字节的窗口。

P6 是 3-wide 超标量,每个时钟周期最多可以解码包含最多 3 条指令的 16 字节机器码。 (但是有一个预解码阶段首先找到指令长度......)

有关详细信息,请参阅 Agner Fog 的微架构指南 (https://agner.org/optimize/),(重点是与提高软件性能相关的细节。)后来的微架构在预解码和解码之间添加了队列。请参阅 Agner Fog 的微架构指南的那些部分,以及 https://realworldtech.com/merom/ (核心 2)。

当然请参阅 https://realworldtech.com/sandy-bridge对于带有 uop 缓存的更现代的 x86。还有 https://en.wikichip.org/wiki/amd/microarchitectures/zen_2#Core对于最近的 AMD。

在阅读任何这些内容之前,为了获得良好的背景,Modern Microprocessors: A 90-Minute Guide! .


对于修改自己代码的核心,参见:Observing stale instruction fetching on x86 with self-modifying code - 这是不同的(而且更难),因为必须从程序顺序中较早指令与较晚指令的代码提取中整理出存储的无序执行。也就是说,商店必须变得可见的时刻是固定的,不像另一个核心,它只是在它发生时发生。

关于assembly - 当带有指令的内存被另一个内核更改时,CPU 流水线会发生什么情况?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67988744/

相关文章:

github - 如何在Github Action中使用Github发行版本号

assembly - C64 上删除侧边框的示例

assembly - ARM算术加法和标志更新

linux - 理解以下 x86 汇编代码

c - 程序集 16h BIOS 调用不起作用

gcc - 更改 GCC 的输出以进行 0-set(清除)操作

linux - 将终端显示的输出写入文件中

c - 将参数从 C 传递给汇编?

assembly - 为什么 Assembly x86_64 系统调用参数不像 i386 那样按字母顺序排列

templates - 需要对 Azure Pipeline 模板 yaml 中的每个循环使用 For 循环而不是