By Jonathan Corbet, January 9, 2026
Gemini translation
https://lwn.net/Articles/1053142/
在 Linux 内核开发中,READ_ONCE() 和 WRITE_ONCE() 宏无处不在,仅前者就有近 8000 处调用。它们是实现众多无锁算法的核心,对于某些设备内存访问也必不可少。因此,你可能会认为,随着内核中 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() 则在同等条件下强制写入。它们还确保访问是原子的——如果一个任务用 READ_ONCE() 读取某个位置,而另一个任务正写入该位置,读取操作将返回写入前或写入后的值,而不是两者的某种随机混合。除了上述保证,这些宏不对编译器或 CPU 施加任何顺序约束,这使它们有别于 smp_load_acquire() 等具有更强顺序要求的宏。
READ_ONCE() 和 WRITE_ONCE() 宏于 2014 年为 Linux 3.18 版本引入。WRITE_ONCE() 最初名为 ASSIGN_ONCE(),在 3.19 开发周期中更名。
在 2025 年的最后一天,Alice Ryhl 发布了一个补丁系列,为 Rust 添加了 READ_ONCE() 和 WRITE_ONCE() 的实现。她指出,一旦有了这些调用,代码中的某些 volatile 读取就可以被替换;该系列中的一个改动是将对 struct file f_flags 字段的访问改为使用 READ_ONCE()。这些宏的实现涉及大量 Rust 宏魔法,但究其根本,它们最终调用的是 Rust 的 read_volatile() 和 write_volatile() 函数。
然而,内核的其他 Rust 开发者对此变更表示反对。Gary Guo 称,他宁愿不暴露 READ_ONCE() 和 WRITE_ONCE(),并建议改用内核 Atomic 模块提供的松弛操作。Boqun Feng 详细阐述了反对理由:
READ_ONCE() 和 WRITE_ONCE() 的问题在于其语义复杂。有时它们用于保证原子性,有时用于防止数据竞争。是的,我们在 Rust 中也遵循 Linux 内核内存模型,但只要可能,我们就需要明确 API 的意图,使用 Atomic::from_ptr().load(Relaxed) 在这方面会有所帮助。
在我看来,READ_ONCE()/WRITE_ONCE() 就像是对几个问题的‘权宜之计’解决方案,拥有它会阻碍我们为并发编程建立更清晰的视图。
换言之,使用 Atomic 模块能让开发者更精确地指定操作所需的保证,使代码的预期(及要求)更加清晰。这一观点似乎占了上风,Ryhl 目前已停止推动将此功能加入内核的 Rust 代码中——至少暂时如此。
如果这一结果持续下去,将带来几个有趣的启示。首先,随着 Rust 代码更深入地进入内核核心,其用于共享数据并发访问的代码将看起来与功能相同的 C 代码大相径庭,即使双方处理的是相同的数据。理解一套无锁数据访问 API 已经颇具挑战;开发者现在可能必须理解两套 API,这无疑不会让任务变得更轻松。
同时,这场讨论也引起了人们对 C 语言端代码的关注。正如 Feng 所指出的,内核中仍存在一些 C 代码,它们在许多情况下假设普通写入是原子的,尽管 C 标准明确说明情况并非如此。Peter Zijlstra 回应称,所有此类代码都应更新,以正确使用 WRITE_ONCE()。仅仅是找到这些代码可能就是个挑战(尽管 KCSAN 可以提供帮助);更新所有这些代码可能需要相当长的时间。对话中还发现,(C 语言的)高分辨率定时器代码中遗漏了一个必要的 READ_ONCE() 调用。这再次印证了 Rust 相关工作也能驱动 C 代码改进。
在过去关于 Rust 抽象设计的讨论中,一直存在一种阻力,反对创建与 C 语言对应接口差异过大的 Rust 接口。例如,如果 Rust 开发者为某个接口构思了更好的设计,那么想法是 C 语言端也应改进以匹配新设计。如果人们接受 Rust 处理 READ_ONCE() 和 WRITE_ONCE() 的方式优于原始方式,那么或许可以得出结论:这里也应遵循类似的流程。然而,更改数以千计的底层并发原语以指定更精确的语义,绝非胆小者所能胜任。最终,这可能导致两种语言的代码分道扬镳。
LWN 评论概述
评论区对 Rust 中挥发性操作的安全性进行了深入讨论。一位开发者分享了在 AVR 架构上使用 read_volatile() 和 write_volatile() 的尝试,虽然汇编输出符合预期,但由于 Rust 内存模型将并发挥发性访问视为不安全,他最终不得不改用内联汇编。其他评论者则聚焦于 C 语言中 READ_ONCE() 和 WRITE_ONCE() 的历史背景,指出这些宏是在 C11 原子模型普及前,利用 volatile 关键字实现的“妥协”方案。讨论认为,虽然从长远来看,将内核代码库中的这些宏替换为语义更明确的松弛原子操作是正确的方向,但考虑到数以千计的调用点及其潜在的微妙依赖,这种大规模重构在 C 语言端极难实现。目前,Rust 社区更倾向于直接采用更现代、更清晰的原子 API。
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。