Linux kernel 与内核共享内存和编译器优化

Linux kernel 与内核共享内存和编译器优化,linux-kernel,x86,memory-model,Linux Kernel,X86,Memory Model,帧与内核共享 用户空间代码: read frame // read frame content _mm_mfence // prevent before "releasing" a frame before we read everything. frame.status = 0 // "release" a frame read frame // read frame content _mm_mfence // prevent before "releasing" a frame b

与内核共享

用户空间代码:

read frame  // read frame content
_mm_mfence  // prevent before "releasing" a frame before we read everything.
frame.status = 0 // "release" a frame
read frame  // read frame content
_mm_mfence  // prevent before "releasing" a frame before we read everything.
frame.status = 0 // "release" a frame
_mm_fence
内核代码:

poll for frame.status // reads a frame's status   
_mm_lfence
内核可以在另一个线程中异步轮询它。因此,在用户空间代码和内核空间之间没有
syscall


它是否正确同步?

我怀疑是因为以下情况:

编译器的内存模型很弱,我们必须假设,如果优化/更改的程序在一个线程内保持一致,它可以像您想象的那样进行大量更改

因此,在我看来,我们需要第二个障碍,因为编译器可能会优化出
store frame.status,0

是的,这将是一个非常疯狂的优化,但如果编译器能够证明上下文(线程内)中没有人读取该字段,那么它就可以优化它

我相信这在理论上是可能的,不是吗

因此,为了防止这种情况,我们可以设置第二个障碍:

用户空间代码:

read frame  // read frame content
_mm_mfence  // prevent before "releasing" a frame before we read everything.
frame.status = 0 // "release" a frame
read frame  // read frame content
_mm_mfence  // prevent before "releasing" a frame before we read everything.
frame.status = 0 // "release" a frame
_mm_fence
好的,现在编译器在优化之前约束自己

你觉得怎么样


编辑

[这个问题是由
优化之前
没有阻止的问题引起的

@PeterCordes,为了确保我自己:
\uMM\uFence
在优化之前不会阻止(它只是x86内存屏障,不是编译器)。然而,
原子线程围栏(任何顺序)
在重新排序之前会阻止(显然,它取决于
任何顺序
),但它在优化之前也会阻止

例如:

   // x is an int pointer
   *x = 5 
   *(x+4) = 6 
   std::atomic_thread_barrier(memory_order_release)
在优化之前防止将存储区外的存储区优化为
x
?似乎必须这样做-否则
x
的每个存储区都应该是
易失的

然而,我看到了很多无锁代码,没有
生成字段
,因为
易失性

也是编译器的障碍。(参见,还有BeeOnRope的答案)

atomic\u thread\u fence
发布后,rel\u acq或seq\u cst会阻止早期存储与后期存储合并。但是
mo\u acquire
不必这样做


对非原子全局变量的写操作只能通过与对相同非原子变量的其他写操作合并来优化,而不能完全优化它们。因此,真正的问题是会发生什么样的重新排序,从而使两个非原子赋值结合在一起

必须在其中的某个位置为原子变量赋值,才能使另一个线程与之同步。某些编译器可能会提供更强的非原子变量行为,但在C++11中,另一个线程无法合法地观察*x
x[4]

#include <atomic>
std::atomic<int> shared_flag {0};
int x[8];

void writer() {
    *x = 0;
    x[4] = 0;
    atomic_thread_fence(mo_release);
    x[4] = 1;
    atomic_thread_fence(mo_release);
    shared_flag.store(1, mo_relaxed);
}
gcc只按该顺序执行
shared_flag=1;*x=2;
。您可能会认为,在看到
shared_flag==1
后,另一个线程无法安全地观察
*x
,因为该线程会立即再次写入,而不会进行同步。(即,任何潜在观察者的数据竞争使得这种重新排序可以说是合法的)

但是gcc开发人员认为这还不够,(它可能违反了
提供的内置
\uuu原子
函数的保证)

:

我认为错误在于,在x86的
\uuuuuuu原子线程围栏(x)
上,对于
x!=\uuuuu原子顺序CST
,它应该放置一个类似于扩展
\uuu原子信号围栏
的编译器屏障

原子信号围栏
包括像
asm(“内存”)
一样强大的东西)


是的,这肯定是一个bug。所以gcc并不是真的很聪明,并且允许重新排序,它只是在
thread\u fence
上失败了,而任何正确性都是由于其他因素造成的,比如非内联函数边界!(而且它没有优化原子,只有非原子。)

除非使用WC内存中的NT加载,否则您永远不需要
\u mm\u lfence
。请使用Linux
smp\u rmb
(读取内存屏障)对于CPU之间的同步。在x86上,它只是一个编译器屏障,没有asm指令。我也不明白为什么需要
mfence
;加载不能在以后的存储中重新排序。mfence对于
store;mfence;load
情况很有意义,可以让线程等待存储全局可见。屏障用于排序。我们e
volatile
atomic
以防止优化加载/存储。或者在Linux中,
读取一次
。当然,我假设是x86。您使用了一个不可移植的
\u mm\u lfence()
。无论如何,您实际需要的是一个发布屏障,Linux内核相当于
atomic\u thread\u fence(内存顺序\u release),将负载保持在<代码>框架中。状态=0</Case> Stor。Linux是否提供了释放存储功能?我不太熟悉Linux内核障碍/功能的细节。IDK为什么你说我“不考虑编译器的重新排序”。<代码> SMPYRMB()
可移植地阻止+运行时重新排序。@Gilgamesz:不,在PowerPC上它可以是
lwsync
。轻量级同步是除StoreLoad之外的所有重新排序的障碍。在SPARC(非TSO)上,发布障碍是#LoadStore和#StoreStore障碍。(在您的情况下,我认为您只需要#LoadStore,因此如果您编写了SPARC版本,您可以省略#StoreStore屏障。x86的sfence只是StoreStoreStore,因此如果x86没有一直阻止除StoreLoad之外的所有内容是不够的,但是弱顺序的ISA与x86有不同的围栏。我可能会使用
ACCESS\u ONCE()
,因为没有理由不这样做。如果使用链接时间优化构建,安全总比抱歉好。@Tsyvarev建议假设您可以