ACCESS_ONCE()/WRITE_ONCE()/READ_ONCE()

几乎每次阅读 linux 源码都会遇到 ACCESS_ONCE,它是用来干什么的, 以及它如何做到的呢?

ACCESS_ONCE() 的原型在 include/linux/compiler.h 中。

#define __ACCESS_ONCE(x) ({ \
         __maybe_unused typeof(x) __var = (__force typeof(x)) 0; \
        (volatile typeof(x) *)&(x); })
#define ACCESS_ONCE(x) (*__ACCESS_ONCE(x))

__ACCESS_ONCE() 第二行 __maybe_unused typeof(x) __var = (__force typeof(x)) 0; 保证了 0 可以赋值 给与参数 x 同类型的变量 __var,也就是说,ACCESS_ONCE 只能用 于标量类型,即 char, short, int, long 以及可以与它们相互转换的类型。

第三行的意义令人费解。 考虑下面一段来自老版本的 kernel/mutex.c 的代码:

for (;;) {
  struct task_struct *owner;

      owner = ACCESS_ONCE(lock->owner);
      if (owner && !mutex_spin_on_owner(lock, owner))
          break;
      /* ... */

一般情况下,如果锁的占有者正在执行,它很可能会在不久后将锁释放,所以尝试获 得锁时,最好判断其占有着是否在执行,如果是则不将其挂起重新调度,在获得锁之 前自旋即可。这段代码正出自此逻辑。

现实世界中有一些极端狂热的想要优化任何性能的家伙,你必须时刻警惕地盯住他们, 当他们脑子里蹦出危险的想法并且眼里冒出刺眼的闪光时阻止他们。 看到这一段代码, 毫无疑问其中会有一些人想修改成下面这样子:

owner = ACCESS_ONCE(lock->owner);
for (;;) {
  if (owner && !mutex_spin_on_owner(lock, owner))
    break;
  /* ... */

这时候如果另一个线程修改了 lock->owner,这段代码是没法知道的,这就造成了 BUG 。

可怕的是,编译器的设计者正式这些极端狂热的性能优化者,他们编写的编译器也正是这么 优化一个循环的。 而 __ACCESS_ONCE() 的第三行通过临时将变量转化为 volatile 类型告诉编译器不能这样做。

READ_ONCE() 和 WRITE_ONCE() 提供了用于非标量的方法,作用和 ACCESS_ONCE() 相同。

ACCESS_ONCE 的注释也说的很清楚:

Prevent the compiler from merging or refetching accesses. The compiler is also forbidden from reordering successive instances of ACCESS_ONCE(), but only when the compiler is aware of some particular ordering. One way to make the compiler aware of ordering is to put the two invocations of ACCESS_ONCE() in different C statements.

ACCESS_ONCE will only work on scalar types. For union types, ACCESS_ONCE on a union member will work as long as the size of the member matches the size of the union and the size is smaller than word size.

The major use cases of ACCESS_ONCE used to be (1) Mediating communication between process-level code and irq/NMI handlers, all running on the same CPU, and (2) Ensuring that the compiler does not fold, spindle, or otherwise mutilate accesses that either do not require ordering or that interact with an explicit memory barrier or atomic instruction that provides the required ordering.

If possible use READ_ONCE()/WRITE_ONCE() instead.

看来这个问题经常被问到。

相关阅读

https://lwn.net/Articles/508991/

http://yarchive.net/comp/linux/ACCESS_ONCE.html

https://github.com/torvalds/linux/blob/master/Documentation/memory-barriers.txt

By .