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 .

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-04-03 · linux

linux 文件I/O

Linux 文件 I/O 深度解析:内核文件表、系统调用与文件描述符管理

2026-03-31 · linux

大多数'无锁'代码其实不是无锁的

拆解 GitHub 高星'无锁'库的真实面目:隐藏的 mutex、被滥用的 memory_order_relaxed、以及 CAS 重试循环的阻塞本质。附 x86 vs ARM 上的行为差异实测。

2026-03-31 · linux

Linux 内核的内存屏障:一个让我调了三天的 bug

一个在 x86 上跑了两年的内核模块,迁移到 ARM 后开始随机丢数据。三天的调试过程教会了我 smp_wmb() 的真正含义。附 Linux 内核屏障 API 完整分类与 x86/ARM 编译产物对比。

2026-04-03 · linux

Linux进程的调度

Linux 进程调度详解:调度类、优先级与实时任务调度机制