x86屏障指令可以简要描述如下:
MFENCE可以防止任何后续的加载或存储在先前的加载或存储之前成为全局可观察到的。它会在后续的加载1执行之前清空存储缓冲区。
LFENCE阻塞指令分派(英特尔的术语),直到所有较早的指令退役。目前,这是通过在后续指令进入后端之前清空ROB(ReOrder Buffer)来实现的。
SFENCE仅对存储器进行排序,即防止NT存储器从存储器缓冲区中提前提交。但除此之外,SFENCE就像一个普通的存储器一样通过存储器缓冲区移动。可以将其视为在杂货店结账传送带上放置隔板,以防止NT存储器被提前抓取。它不一定强制存储器缓冲区在从ROB退役之前被清空,因此在其后面放置LFENCE并不等同于MFENCE。
像CPUID(和IRET等)这样的“序列化指令”会在后续指令进入后端之前清空所有内容(ROB、存储器缓冲区),并且会丢弃前端。MFENCE + LFENCE也可以完成后端部分,但真正的序列化指令还会丢弃获取的机器代码,因此可以用于交叉修改代码。(例如,一个加载看到一个标志,你运行cpuid
或新的serialize
,然后跳转到一个缓冲区,在那里另一个线程在标志上发布存储器之前存储了代码。代码获取保证可以获得新的指令。与数据加载不同,代码获取不遵守x86的常规LoadLoad排序规则。)
在具体订购哪些操作方面,这些描述有点模糊,不同厂商之间存在一些差异(例如,AMD上的SFENCE更强),甚至是来自同一厂商的处理器也有所不同。请参考Intel的手册和规格更新以及AMD的手册和修订指南以获取更多信息。还有很多其他关于这些指令的讨论,可以在SO和其他地方找到。但首先要阅读官方来源。我认为以上描述是跨厂商最小指定的纸面行为。
脚注1:后期存储的OoO exec不需要被MFENCE阻塞;执行它们只是将数据写入存储缓冲区。按顺序提交已经将它们排序在早期存储之后,并在退休后根据负载排序(因为x86要求负载完成,而不仅仅是开始,在它们可以退休之前,作为确保负载排序的一部分)。
请记住,x86硬件构建时禁止重新排序,除了StoreLoad。
Intel手册第2卷号码325383-072US描述了SFENCE指令,它“确保在SFENCE之前的每个存储在SFENCE之后的任何存储变得全局可见之前都是全局可见的。”第3节第11.10节说,在使用SFENCE时会清空存储缓冲区。这个声明的正确解释与第2卷中的早期声明完全相同。因此,可以说SFENCE在某种意义上排空了存储缓冲区。在SFENCE的生命周期中,没有保证早期存储何时达到GO。对于任何早期存储,它可能发生在SFENCE退役之前、期间或之后。关于GO的目的,这取决于几个因素。这超出了问题的范围。请参见:
为什么“movnti”后跟“sfence”可以保证持久排序?。
MFENCE需要防止NT存储与其他存储重新排序,因此它必须包括SFENCE的所有内容,并排空存储缓冲区。此外,还需要防止来自WC内存的弱序SSE4.1 NT加载重新排序,这更加困难,因为通常免费获取加载排序的规则不再适用于这些规则。保证这一点是为什么Skylake微码更新加强了(并减慢了)MFENCE以像LFENCE一样排空ROB。如果有硬件支持可选地强制执行管道中的NT加载排序,则MFENCE仍可能比那更轻量级。
SFENCE + LFENCE不等于MFENCE的主要原因是,SFENCE + LFENCE不能阻止StoreLoad重排序,所以它对于顺序一致性来说是不足够的。只有mfence
(或者一个lock
操作,或者一个真正的序列化指令,例如cpuid
)才能做到这一点。请参见Jeff Preshing的Memory Reordering Caught in the Act,了解只有完整屏障才足够的情况。
来自Intel的指令集参考手册sfence
条目:
在SFENCE之前的每个存储都在SFENCE之后的任何存储变为全局可见之前,处理器确保它们已经全局可见。
但是
它与内存加载或LFENCE指令的顺序无关。
LFENCE 会强制之前的指令“本地完成”(即从乱序部分退役),但对于存储或 SFENCE,这只意味着将数据或标记放入内存顺序缓冲区,而不是刷新它,以使存储变为全局可见。即 SFENCE 的“完成”(从 ROB 退役)不包括刷新存储器缓冲区。
这就像 Preshing 在 Memory Barriers Are Like Source Control Operations 中所描述的那样,其中 StoreStore 屏障不是“即时”的。在该文章的后面,他解释了为什么 #StoreStore + #LoadLoad + #LoadStore 屏障不等于 #StoreLoad 屏障。(x86 LFENCE 具有一些额外的指令流串行化,但由于它不刷新存储器缓冲区,因此推理仍然成立)。
LFENCE不能像cpuid
一样完全序列化 (它只是LoadLoad + LoadStore障碍,加上一些执行序列化的内容,可能最初是一种实现细节,但现在已经成为Intel CPU上至少保证的东西,就像mfence
或lock
ed指令一样强的内存屏障)。它对于rdtsc
很有用,并且可以避免分支推测以减轻Spectre漏洞。
顺便提一句,对于WB(正常)存储,SFENCE是无操作的。
它会将WC存储(例如movnt或存储到视频RAM的存储器)与任何存储器相关联,但不会与加载或LFENCE相关。只有在通常为弱序的CPU上,存储-存储屏障才会对正常存储器起作用。除非您正在使用NT存储器或将内存区域映射为WC,否则您不需要SFENCE。如果确保在可退休之前排空存储器缓冲区,则可以使用SFENCE + LFENCE构建MFENCE,但这并非适用于英特尔系统。
真正的问题是存储和加载之间的StoreLoad重排序,而不是存储和屏障之间的重排序,因此您应该查看一个存储,然后是一个屏障,最后是一个加载的情况。
mov [var1], eax
sfence
lfence
mov eax, [var2]
可以按照以下顺序变为全局可见(即提交到L1d缓存):
lfence
mov eax, [var2]
mov [var1], eax
sfence
内存控制器
强制执行的。Fences 用于协调系统内存和缓存内存。而我认为这种缓存一致性是内存控制器
的责任。 - Peng ZhangL/S/MFENCE
与缓存一致性无关,因为SFENCE
仅清除与缓存一致性无关的存储缓冲区。在某些CPU(例如非x86)上,Load FENCE可以刷新Invalidate-Queue,但x86没有此功能。在互联网上,我发现LFENCE对于x86处理器没有意义,也就是说它什么都不做。那么,SFENCE
MOV reg,[addr]
-->MOV reg,[addr]
的重排理论上可能存在,但实际上却不可能,这是真的吗? - Alex