一尘不染

内存屏障和Linux上的atomic_t

linux

最近,我正在阅读一些Linux内核空间代码,我看到了

uint64_t used;
uint64_t blocked;

used = atomic64_read(&g_variable->used);       //#1
barrier();                                     //#2
blocked = atomic64_read(&g_variable->blocked); //#3

该代码段的语义是什么?是否确保#1在#3之前由#2执行。但是我有点乱,因为

#A 在64位平台上,atomic64_read宏扩展为

used = (&g_variable->used)->counter           // where counter is volatile.

在32位平台中,将其转换为使用锁 cmpxchg8b 。我认为这两个具有相同的语义,对于64位版本,我认为这意味着:

  1. all-or-nothing ,我们可以排除地址未对齐且字长大于CPU本机字长的情况。
  2. 没有优化 ,强制CPU从内存位置读取。

atomic64_read没有保留读取顺序的语义!!! 看到这个

#B屏障 宏定义为

/* Optimization barrier */
/* The "volatile" is due to gcc bugs */
#define barrier() __asm__ __volatile__("": : :"memory")

Wiki中,这只是防止
gcc编译器 重新排列读写顺序。

我很困惑的是它如何禁用CPU的重新排序优化?另外,我可以认为屏障宏是完整的栅栏吗?


阅读 742

收藏
2020-06-07

共1个答案

一尘不染

32位x86处理器不能为64位类型提供简单的原子读取操作。在此类处理“普通”寄存器的CPU上,对64位类型唯一的原子操作是LOCK CMPXCHG8B,这就是为什么在此使用它的原因。另一种选择是使用MOVQ和MMX /
XMM寄存器,但是这需要了解FPU状态/寄存器,并且需要使用MMX / XMM指令完成对该值的所有操作。

在64位x86_64处理器上,对64位类型的对齐读取是原子的,并且可以通过一条MOV指令完成,因此仅需要进行普通读取—
的使用volatile只是为了确保编译器实际进行读取,并且不缓存先前的值。

至于读取顺序,您引用的内联汇编程序可确保编译器以正确的顺序发出指令,这是x86 / x86_64
CPU所需要的,只要写入顺序正确即可。LOCKed在x86上的写入具有总顺序;普通MOV写提供了“因果一致性”,因此,如果线程A做到了,x=1那么y=2如果线程B进行了读取,y==2则随后的读取x将看到x==1

在IA-64中,的PowerPC,SPARC,和具有更宽松的存储器模型其它处理器很可能有更多的atomic64_read()barrier()

2020-06-07