LWN: READ_ONCE(), WRITE_ONCE(),但不适用于 Rust
在 2025 年的最后一天,Alice Ryhl 发布了一个 patch series (补丁系列),为 Rust 添加了 READ_ONCE() 和 WRITE_ONCE() 的实现。WRITE_ONCE() 则在这些条件下强制进行写入。所以是的,我们在 Rust 中也使用 Linux kernel memory model (Linux 内核内存模型, LKMM),但只要有可能,我们就需要明确
关注了就能看到更多这么棒的文章哦~
By *Jonathan Corbet*, January 9, 2026
Gemini translation
https://lwn.net/Articles/1053142/
READ_ONCE() 和 WRITE_ONCE() 宏在内核中被广泛使用;READ_ONCE() 的调用点就有近 8,000 处。它们是实现许多 lockless algorithms (无锁算法) 的关键,对于某些类型的设备内存访问也是必不可少的。因此,人们可能会认为,随着内核中 Rust 代码量的增加,这些宏的 Rust 版本也会有一席之地。然而,事实情况是,Rust 社区似乎想对并发数据访问采取不同的处理方式。
对于任何处理数据并发访问的内核开发者来说,理解 READ_ONCE() 和 WRITE_ONCE() 至关重要。然而,自然而然地,它们在内核文档中几乎完全缺席。在 include/asm-generic/rwonce.h 的顶部可以找到某种程度的描述:
防止编译器合并或重新获取读取或写入。编译器也被禁止对连续的 READ_ONCE 和 WRITE_ONCE 实例进行重排序,但仅当编译器意识到某些特定顺序时。让编译器意识到顺序的一种方法是将两次 READ_ONCE 或 WRITE_ONCE 调用放在不同的 C 语句中。
换句话说,READ_ONCE() 调用将强制编译器从指定位置精确读取一次,不使用任何会导致读取被省略或重复的优化技巧;WRITE_ONCE() 则在这些条件下强制进行写入。它们还将确保访问是 atomic (原子的);如果一个任务使用 READ_ONCE() 读取某个位置,而另一个任务正在写入该位置,则读取将返回写入前或写入后的值,而不是两者的某种随机组合。除了上述描述外,这些宏不对编译器或 CPU 施加任何顺序约束,这使它们有别于 smp_load_acquire() 等具有更强顺序要求的宏。
READ_ONCE() 和 WRITE_ONCE() 宏是在 2014 年为 3.18 版本添加的。WRITE_ONCE() 最初被称为 ASSIGN_ONCE(),但在 3.19 开发周期中更改了名称。
在 2025 年的最后一天,Alice Ryhl 发布了一个 patch series (补丁系列),为 Rust 添加了 READ_ONCE() 和 WRITE_ONCE() 的实现。她说,代码中有些地方一旦有了这些调用,就可以替换掉 volatile reads (挥发性读取);在其他更改中,该系列将对 struct file f_flags 字段的访问改为使用 READ_ONCE()。这些宏的实现涉及大量 Rust 宏魔法,但归根结底,它们最终调用的是 Rust 的 read_volatile() 和 write_volatile() 函数。
然而,一些其他的内核 Rust 开发者反对这一变化。Gary Guo 表示,他宁愿不暴露 READ_ONCE() 和 WRITE_ONCE(),并建议改用内核 Atomic 模块提供的 relaxed operations (松弛操作) 代替。Boqun Feng 详细阐述了反对意见:
READ_ONCE() 和 WRITE_ONCE() 的问题在于其语义复杂。有时它们用于 atomicity (原子性),有时用于防止数据竞争。所以是的,我们在 Rust 中也使用 Linux kernel memory model (Linux 内核内存模型, LKMM),但只要有可能,我们就需要明确 API 的意图,使用 Atomic::from_ptr().load(Relaxed) 在这方面会有所帮助。
在我看来,READ_ONCE()/WRITE_ONCE() 就像是对几个问题的“权宜之计”解决方案,拥有它会阻碍我们为并发编程建立更清晰的视图。
换句话说,使用 Atomic 模块允许开发者更精确地指定操作所需的保证,使代码的预期(和要求)更加清晰。这一观点似乎占据了上风,Ryhl 已经停止推动将此功能添加到内核的 Rust 代码中——至少目前是这样。
如果这一结果维持下去,会有几个有趣的启发。首先是,随着 Rust 代码更深入地进入内核核心,其用于共享数据并发访问的代码将看起来与等效的 C 代码大不相同,即使双方代码可能都在处理相同的数据。在处理一个 API 时,理解无锁数据访问已经足够具有挑战性了;开发者现在可能必须理解两个 API,这不会让任务变得更容易。
与此同时,这场讨论也引起了对 C 语言端代码的一些关注。正如 Feng 指出的,内核中仍然存在一些 C 代码,它们在许多情况下假设普通写入是 atomic (原子的),尽管 C 标准明确规定并非如此。Peter Zijlstra 回应称,所有此类代码都应更新以正确使用 WRITE_ONCE()。简单地寻找这些代码可能就是一个挑战(尽管 KCSAN 可以提供帮助);更新所有这些代码可能需要一段时间。对话还发现(C 语言的) high-resolution-timer (高分辨率定时器) 代码中漏掉了一个必要的 READ_ONCE() 调用。这是 Rust 工作带动 C 代码改进的又一个例子。
在过去关于 Rust 抽象设计的讨论中,对于创建看起来与 C 语言对应接口大不相同的 Rust 接口一直存在阻力。例如,如果 Rust 开发者为某个接口想出了更好的设计,那么想法是 C 语言端也应该改进以匹配这个新设计。如果人们接受 Rust 处理 READ_ONCE() 和 WRITE_ONCE() 的方式比原始方式更好,那么可能会得出结论,这里也应该遵循类似的流程。然而,更改数千个底层并发原语以指定更精确的语义,绝非胆小者所能胜任。这最终可能演变成两种语言的代码各行其道的情况。
LWN 评论概述
评论区对 Rust 中挥发性操作的安全性进行了深入讨论。一位开发者分享了在 AVR 架构上使用 read_volatile() 和 write_volatile() 的尝试,虽然汇编输出符合预期,但由于 Rust 内存模型将并发挥发性访问视为不安全(unsound),他最终不得不改用内联汇编。其他评论者则聚焦于 C 语言中 READ_ONCE() 和 WRITE_ONCE() 的历史背景,指出这些宏是在 C11 原子模型普及前,利用 volatile 关键字实现的“妥协”方案。讨论认为,虽然从长远来看,将内核代码库中的这些宏替换为语义更明确的松弛原子操作(relaxed atomics)是正确的方向,但考虑到数以千计的调用点及其潜在的微妙依赖,这种大规模重构在 C 语言端极难实现。目前,Rust 社区倾向于直接采用更现代、更清晰的原子 API。
全文完LWN 文章遵循 CC BY-SA 4.0 许可协议。
欢迎分享、转载及基于现有协议再创作~
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~
更多推荐

所有评论(0)