x86 架构上存在的内存屏障——但通常情况下确实如此——不仅保证所有先前的1 加载或存储在执行任何后续加载或存储之前完成——他们还保证这些商店已经全球可见。
全局可见意味着其他缓存感知代理(如其他 CPU)可以看到存储。
如果目标内存已被标记为不强制立即写入内存的缓存类型,则其他不知道缓存的代理(例如支持 DMA 的设备)通常不会看到存储。
这与屏障本身无关,这是 x86 架构的一个简单事实:程序员可以看到缓存,而在处理硬件时,它们通常会被禁用。
英特尔特意对障碍的描述通用,因为它不想将自己与特定的实施联系起来。
您需要抽象地思考:全局可见意味着硬件将采取所有必要的步骤使商店全局可见。期间。
然而,要了解障碍,有必要看看当前的实现。
请注意,只要保持可见行为正确,英特尔就可以随意颠倒现代实施。
x86 CPU 中的存储在内核中执行,然后放入 存储缓冲区。
例如mov DWORD [eax+ebx*2+4], ecx,一旦解码就会停止,直到eax、ebx 和ecx 准备就绪2,然后将其分派到能够计算其地址的执行单元。
执行完成后,存储变成了一对 (address, value),被移动到 存储缓冲区。
据说商店在本地完成(在核心中)。
存储缓冲区允许 CPU 的 OoO 部分忘记存储并认为它已完成,即使尚未尝试写入也是如此。
在发生特定事件时,例如序列化事件、异常、屏障的执行或缓冲区耗尽,CPU 会刷新存储缓冲区。
刷新总是有序的 - 先入先出。
存储从存储缓冲区进入缓存领域。
如果目标地址被标记为 WC 缓存类型,它可以被组合到另一个称为 Write Combining 缓冲区 的缓冲区中(然后绕过缓存写入内存),它可以被写入如果缓存类型是 WB 或 WT,则 L1D 缓存、L2、L3 或 LLC(如果不是前一种)。
如果缓存类型为UC或WT,也可以直接写入内存。
今天这就是成为全球可见的意思:离开存储缓冲区。
请注意两件非常重要的事情:
- 缓存类型仍会影响可见性。
全局可见并不意味着在内存中可见,它意味着在其他内核的负载会看到它的地方可见。
如果内存区域是 WB 可缓存的,则加载可能会在缓存中结束,因此它在此处是全局可见的 - 只有代理知道缓存的存在。 (但请注意,现代 x86 上的大多数 DMA 都是缓存一致的)。
- 这也适用于不连贯的 WC 缓冲区。
WC 没有保持连贯性 - 它的目的是将存储合并到顺序无关紧要的内存区域,例如帧缓冲区。这不是真正全局可见的,只有在写入组合缓冲区被刷新后,核心之外的任何东西才能看到它。
sfence 正是这样做的:等待所有以前的存储在本地完成,然后清空存储缓冲区。
由于存储缓冲区中的每个存储都可能会丢失,因此您会看到此类指令有多么繁重。 (但包括后续加载在内的乱序执行可以继续。只有mfence 会阻止以后的加载在全局可见(从 L1d 缓存中读取),直到存储缓冲区完成提交到缓存之后。)
但是sfence 是否等待存储传播到其他缓存中?
嗯,没有。
因为没有传播——让我们从高级的角度来看看写入缓存意味着什么。
缓存在所有处理器之间通过 MESI 协议保持一致(MESIF 用于多插槽 Intel 系统,MOESI 用于 AMD 系统)。
我们只会看到 MESI。
假设写入索引缓存行 L,并假设所有处理器在其缓存中都有此行 L 具有相同的值。
此行的状态是Shared,在每个 CPU 中。
当我们的存储进入缓存时,L 被标记为 Modified,并且在内部总线(或多插槽 Intel 系统的 QPI)上进行特殊事务以使其他处理器中的行 L 无效.
如果 L 最初不是处于 S 状态,则相应地更改协议(例如,如果 L 处于状态 Exclusive 上没有事务公共汽车已经完成[1])。
此时写入完成,sfence 完成。
这足以保持缓存的一致性。
当另一个 CPU 请求行 L 时,我们的 CPU 侦听该请求并将 L 刷新到内存或内部总线,因此另一个 CPU 将读取更新的版本。
L 的状态再次设置为S。
所以基本上 L 是按需读取的 - 这是有道理的,因为将写入传播到其他 CPU 是昂贵的,并且一些架构通过将 L 写回内存来做到这一点(这是有效的,因为其他 CPU 的 L 处于状态 Invalid 所以它必须从内存中读取它)。
最后,sfence 等通常都是无用的,这不是真的,相反,它们非常有用。
只是通常我们并不关心其他 CPU 是如何看待我们进行存储的——但是在没有获取语义的情况下获取锁,例如在 C++ 中定义并使用栅栏实现,是完全疯了。
您应该像英特尔所说的那样考虑障碍:它们强制执行内存访问的全局可见性顺序。
您可以通过将障碍视为强制执行顺序或写入缓存来帮助您自己理解这一点。然后,缓存一致性将确保对缓存的写入是全局可见的。
我不得不再次强调,缓存一致性、全局可见性和内存排序是三个不同的概念。
第一个保证第二个,由第三个强制执行。
Memory ordering -- enforces --> Global visibility -- needs -> Cache coherency
'.______________________________'_____________.' '
Architectural ' '
'._______________________________________.'
micro-architectural
脚注:
- 按程序顺序。
-
这是一个简化。在 Intel CPU 上,mov [eax+ebx*2+4], ecx 解码为两个独立的微指令:存储地址和存储数据。存储地址 uop 必须等到 eax 和 ebx 准备好,然后将其分派到能够计算其地址的执行单元。该执行单元writes the address into the store buffer,因此以后的加载(按程序顺序)可以检查存储转发。
当ecx 准备好时,store-data uop 可以分派到store-data 端口,并将数据写入同一个存储缓冲区条目。
这可能发生在地址已知之前或之后,因为存储缓冲区条目可能按程序顺序保留,因此存储缓冲区(也称为内存顺序缓冲区)可以跟踪加载/存储顺序一旦所有内容的地址最终知道,并检查重叠。 (对于最终违反 x86 内存排序规则的推测性负载,如果另一个内核使他们在架构上允许加载的最早点之前加载的缓存行无效。这会导致 a memory-order mis-speculation pipeline clear。)