12

最近在看一些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 位平台中,它被转换为使用 lock 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 的重新排序优化?另外,我可以认为屏障宏是完整的围栏吗?

4

2 回答 2

8

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

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

至于读取顺序,您引用的内联汇编程序确保编译器以正确的顺序发出指令,这就是 x86/x86_64 CPU 所需的全部,前提是写入顺序正确。LOCKx86 上的 ed 写入具有总排序;普通MOV写入提供“因果一致性”,因此如果线程 A 这样做,x=1那么y=2如果线程 B 读取,y==2那么后续读取x将看到x==1

在 IA-64、PowerPC、SPARC 和其他具有更宽松内存模型的处理器上,很可能会有更多的atomic64_read()barrier().

于 2011-07-04T08:42:47.413 回答
4

x86 CPU 不执行 read-after-read 重新排序,因此足以防止编译器进行任何重新排序。在 PowerPC 等其他平台上,情况会大不相同。

于 2011-07-04T06:25:38.357 回答