在x86上, lock
等前缀指令(如 lock cmpxchg
)除了原子操作外还提供屏障语义:对于回写内存区域的正常内存访问,读取和写入不按 lock
-前缀指令重新排序,按照8.2.2节英特尔SDM第3卷:
读取或写入不能使用I / O指令,锁定指令或序列化指令进行重新排序 .
本节仅适用于回写内存类型 . 在同一个列表中,您会发现一个例外情况,它指出没有订购弱排序的商店:
读取不会与其他读取重新排序 . 写入不会与较旧的读取重新排序 . 写入内存不会与其他写入重新排序,但以下情况除外: - 使用非时间移动指令(MOVNTI,MOVNTQ,MOVNTDQ,MOVNTPS和MOVNTPD)执行的流存储(写入);和 - 字符串操作(参见第8.2.4.1节) .
注意,列表中的任何其他项目中的非时间指令没有例外,例如,在涉及锁定前缀指令的项目中 .
在本指南的各个其他部分中,提到当使用弱有序(非时间)指令时, mfence
和/或 sfence
指令可用于对存储器进行排序 . 这些部分通常不提及 lock
-prefixed指令作为替代 .
所有这些让我不确定: lock
-prefixed指令是否提供了 mfence
在WB内存上的弱有序(非时间)指令之间提供的完全屏障?同样的问题再次适用于WC内存的任何类型的访问 .
2 回答
锁定前缀的指令比Intel的
mfence
强大得多.AMD64的mfence
是一个完全序列化的指令,所以它比Lock-prefixed指令强 . 还有32-bit x86 AMD processors支持mfence
,我希望它的行为与AMD64相同 . 本答案的其余部分仅适用于英特尔的mfence
.紧接在锁定前缀指令之前或之后的
mfence
是多余的 . 从第8.2.5节:(这里的锁定指令是指隐含
lock
的那些 . 在手册的其他地方,它们也明确地引用了前缀为_2360395的指令 . )这里“更强”意味着比第8.2.2节(问题中引用)中讨论的默认排序更强 . 也是从8.2.5:
第8.3节讨论了序列化指令,它根本没有提到锁定前缀 . 但它说:
值得注意的是,锁定前缀不会像第8.3节中列出的那样进行序列化指令 . 主要区别在于锁定前缀允许获取以下指令 . 另外,关于软件预取指令不订购锁定前缀指令 . 从英特尔手册V2:
这同样适用于所有软件预取指令,而不仅仅是
PREFETCHh
.使锁前缀比_2360397更强大的原因是它不仅提供了所有指令(软件预取除外)的序列化,还锁定了对共享内存的访问,以便所有其他逻辑处理器无法访问内存,直到它退休,从而提供原子性 .
现在我've seen the quote from Necrolis'的答案说明了锁定前缀 may not serialize加载操作,它引用了弱有序的内存类型 . 但我认为这个陈述非常陈旧,并且在英特尔不想公开锁定前缀的完整保证的时候为非常老的处理器编写 . 此外,引用只说"may not",这并不是真的矛盾 .
这也可以从AMD手册V2 7.4.2中确认:
脚注:(1)似乎Haswell,Ivy Bridge,Sandy Bridge和可能更早的微体系结构都有一个错误,其中锁定前缀的指令不能序列化NT负载,但
MFENCE
可以 . 在Skylake上,两个指令都无法序列化NT负载 . 见:Does lock xchg have the same behavior as mfence? . 然而,在Broadla和Skylake之后的处理器上,似乎两个指令都可以序列化NT负载 .@ PeterCordes的experiments表明,在Skylake上,锁定指令似乎不会阻止ALU指令无序执行,而
mfence
会对ALU指令进行序列化(可能与lfence
存储缓冲区刷新一样,与锁定指令相同) . 但是,我认为这是一个实现细节 .总线锁(通过
LOCK
操作码前缀)产生一个完整的栅栏*,但是,在WC存储器上它们不提供负载栅栏,这在英特尔64和IA-32架构软件开发人员手册,卷3A,8.1中有记载 . 2:*有关示例,请参阅英特尔64和IA-32架构软件开发人员手册,第3A卷,8.2.3.9