内存屏障和linux上的atomic_t

时间:2011-07-02 06:28:49

标签: linux multithreading concurrency atomic barrier

最近,我正在阅读一些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之前执行#1。 但是我有点困惑,因为

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

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

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

  1. all-or-nothing ,我们可以排除地址未对齐且字大小大于CPU本机字大小的情况。
  2. 无优化,强制CPU从内存位置读取。
  3. atomic64_read没有保留读取顺序的语义!!! 请参阅this

    #B 障碍宏定义为

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

    wiki this只是阻止 gcc编译器重新排序读写。

    我感到困惑的是它如何禁用CPU的重新排序优化?另外,我认为屏障宏是完全围栏吗?

2 个答案:

答案 0 :(得分:8)

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

在64位x86_64处理器上,64位类型的对齐读取是原子的,可以使用MOV指令完成,因此只需要进行简单读取 - 使用{{1}只是为了确保编译器实际执行读操作,而不是缓存先前的值。

对于读取顺序,您引用的内联汇编程序确保编译器以正确的顺序发出指令,这是x86 / x86_64 CPU上所需的全部内容,前提是写入正确排序。 x86上的volatile ed写入总排序;普通LOCK写提供“因果一致性”,因此如果线程A MOV然后x=1,则如果线程B读取y=2,则后续读取y==2将见x

在IA-64,PowerPC,SPARC和其他具有更宽松内存模型的处理器上,x==1atomic64_read()可能会更多。

答案 1 :(得分:4)

x86 CPU不执行读后读取重新排序,因此阻止编译器进行任何重新排序就足够了。在PowerPC等其他平台上,情况会有很大不同。