给定一个 C++ 代码片段:
\n\nint a = 0;\natomic<int> b{0};\n\nThread 1 \na = 1;\nb.store(1,memory_order_release);\n\nThread 2\nwhile(!b.load(memory_order_acquire)); \nassert(a==1);\nRun Code Online (Sandbox Code Playgroud)\n\n我们知道断言永远不会触发。
\n\n另一方面,golangatomic.Store隐含内存屏障的 xchg 指令,因此它可以导致与 c++11 一样的 memory_order_release 语义。
\n\n//go:noescape\nfunc Store(ptr *uint32, val uint32)\nTEXT runtime\xe2\x88\x95internal\xe2\x88\x95atomic\xc2\xb7Store(SB), NOSPLIT, $0-12\n MOVQ ptr+0(FP), BX\n MOVL val+8(FP), AX\n XCHGL AX, 0(BX)\n RET\nRun Code Online (Sandbox Code Playgroud)\n\n然而, atomic.Load的实现是纯go代码,这意味着汇编时只是mov指令。
\n\n//go:nosplit\n//go:noinline\nfunc Load(ptr *uint32) uint32 {\n return *ptr\n}\nRun Code Online (Sandbox Code Playgroud)\n\n那么,golangatomic.Load有acquire语义吗?
\n如果它是如何工作的,如果不是,如何确保内存排序或使 a=1 可见?
因此还有很多问题,例如https://mirrors.edge.kernel.org/pub/linux/kernel/people/paulmck/perfbook/perfbook.2018.12.08a.pdf和Preshing的文章如https:/ /preshing.com/20120710/memory-barriers-are-like-source-control-operations/及其整个系列文章就不同的障碍类型提供的排序和可见性保证方面抽象地讨论了内存排序。我的问题是,如何在x86和ARM微体系结构上实现这些障碍和内存排序语义?
对于商店-商店壁垒,好像在x86上,商店缓冲区保持商店的程序顺序并将它们提交到L1D(因此使它们以相同的顺序在全局可见)。如果存储缓冲区未排序,即未按程序顺序维护它们,那么如何实现存储障碍?它只是以这样的方式“标记”存储缓冲区,即在屏障提交之前将存储提交到缓存一致性域,然后在屏障之后提交?还是存储屏障实际上刷新了存储缓冲区并暂停了所有指令,直到刷新完成?可以同时实现吗?
对于负载障碍,如何防止负载重新排序?很难相信x86将按顺序执行所有加载!我假设加载可以乱序执行,但是可以按顺序提交/退出。如果是这样,如果一个cpu在2个不同的位置执行2次加载,那么一个加载如何确保它从T100中得到一个值,而下一个加载在T100上或之后得到它?如果第一个负载未命中高速缓存并正在等待数据,而第二个负载命中并获取其值,该怎么办。当负载1获得其值时,如何确保它获得的值不是来自该负载2的值的较新商店?如果负载可以无序执行,如何检测到违反内存排序的情况?
类似地,如何实现负载存储屏障(在x86的所有负载中都是隐含的)以及如何实现存储负载屏障(例如mfence)?即dmb ld / st和dmb指令在ARM上是如何微体系结构的?每个负载和每个存储区以及mfence指令在x86上如何进行微体系结构,以确保内存排序?
x86 x86-64 cpu-architecture memory-barriers micro-architecture