当不同的 CPU 核心在不同步的情况下写入相同的 RAM 地址时会发生什么?

What happens when different CPU cores write to the same RAM address without synchronization?

让我们假设 2 个核心试图在同一时刻(正负 eta)向同一 RAM 地址(1 字节)写入不同的值,并且不使用任何互锁指令或内存屏障。在这种情况下会发生什么情况,将向主 RAM 写入什么值?第一个赢了?最后一个赢了?未确定的行为?

它们最终将被排序,很可能在 L1 缓存之间。一个写入将首先出现,另一个将排在第二位。哪个排在第二位,就是后续阅读看到的结果。

x86(与所有其他主流 SMP CPU 架构一样)具有 coherent data caches。两个不同的缓存(例如 2 个不同内核的 L1D)不可能为同一缓存行保存冲突的数据。

硬件强加了一个顺序(通过某些 implementation-specific 机制来打破联系,以防两个所有权请求在同一时钟周期内从不同的内核到达)。在大多数现代 x86 CPUs 中,第一个存储不会写入 RAM,因为有一个共享的 write-back L3 缓存来吸收一致性流量而无需 round-trip 到内存。

出现在全局顺序中两个商店之后的加载将看到第二个商店存储的值。


(我假设我们正在谈论普通(不是 NT)存储到可缓存内存区域(WB,不是 USWC、UC,甚至是 WT)。两种情况下的基本思想都是相同的,尽管; 一个store先走,下一个store踩在上面. 如果在全局顺序中碰巧有负载在它们之间,可以暂时观察到第一个store的数据,否则硬件选择执行第二个操作的存储中的数据将是 long-term 效果。

我们讨论的是单个字节,因此存储不能跨两个缓存行拆分,因此每个地址自然对齐,因此 中的所有内容都适用。


通过要求核心在它可以修改它之前获得对该缓存行的独占访问权来维持一致性(即创建一个存储通过将其从存储队列提交到 L1D 缓存来全局可见。

这个 "acquiring exclusive access" 东西是使用 the MESI protocol 的(变体)完成的。缓存中的任何给定行都可以是修改的(脏的)、独占的(由尚未写入的拥有)、共享的(干净的副本;其他缓存也可能有副本,因此在写入之前需要 RFO(读取/请求所有权)),或无效的。 MESIF (Intel) / MOESI (AMD) 增加额外的状态来优化协议,但不改变任何时候只有一个核心可以改变一条线的基本逻辑。

如果我们关心对两条不同行的多次更改的排序,那么内存排序和内存屏障就会发挥作用。但是 none 对于这个关于 "which store wins" 存储在同一时钟周期内执行或退出的问题很重要。

当存储执行时,它进入存储队列。它可以提交到 L1D 并在任何时间 退休之后变得全局可见,但不能在退休之前;未退出的指令被视为推测性的,因此它们的架构效果在 CPU 核心之外必须不可见。投机负载没有架构效果,只有微架构1.

因此,如果两个存储都准备好在 "the same time" 提交(时钟不一定在内核之间同步),一个或另一个将首先使其 RFO 成功并获得独占访问权,并将其存储数据全局化可见的。然后,不久之后,另一个核心的 RFO 将成功并使用其数据更新缓存行,因此它的存储在所有其他核心观察到的全局存储顺序中排在第二位。

x86 有一个 total-store-order 内存模型,其中所有内核遵守 相同的 顺序,即使存储到不同的缓存行也是如此(除了始终按照程序顺序查看自己的存储).某些 weakly-ordered 体系结构(如 PowerPC)将允许某些内核看到与其他内核不同的总顺序,但这种重新排序只能发生在不同行的商店之间。单个缓存行始终只有一个修改顺序。 (相对于彼此和其他存储重新排序负载意味着您​​必须小心如何在弱排序的 ISA 上观察事物,但是 MESI 强加的缓存行只有一个修改顺序)。

哪一个赢得比赛可能取决于一些平淡无奇的事情,例如环形总线上的内核布局相对于该线路映射到共享 L3 高速缓存的哪一部分。 (注意 "race" 这个词的用法:这是 "race condition" bug 所描述的那种竞争。编写两个不同步的存储更新同一位置的代码并不总是错误的,你不关心哪个一个赢了,但很少见。)

顺便说一句,现代 x86 CPUs 在多个内核争用原子 read-modify-write 到同一缓存行的情况下具有硬件仲裁(因此 ), but regular loads/stores only need to own a cache line for a single cycle to execute a load or commit a store. I think the arbitration for locked instructions is a different thing from which core wins when multiple cores are trying to commit stores to the same cache line. Unless you use a pause instruction, cores assume that other cores aren't modifying the same cache line, and speculatively load early, and thus will suffer memory-ordering mis-speculation if it does happen. (

IDK 如果当两个线程都只是存储而不加载时发生类似的事情,但可能不是因为存储不是推测性重新排序并且与存储队列的 out-of-order 执行分离。一旦存储指令退出,存储肯定会发生,因此 OoO exec 不必等待它实际提交。 (事实上​​ ,它 必须 在它可以提交之前从 OoO 核心退出,因为这就是 CPU 知道它是 non-speculative 的方式;即没有更早的指令出错或者是一个错误预测的分支)


脚注:

  1. Spectre 通过使用 cache-timing 攻击将微架构状态读入架构状态,模糊了这条线。