首页
/
新闻
/
愚蠢的RCU技巧:边界案例RCU实现
愚蠢的RCU技巧:边界案例RCU实现
摘要
Paul McKenney 讨论了非常规和边界情况的 RCU(读-拷贝-更新)实现,包括早期 Unix 系统中使用的定时等待 RCU 方法,以及与内存隔离相关的固定缓冲区 RCU 概念,展示了内核开发中具有创造性但潜在危险的同步技术。
<p><a href="https://lobste.rs/s/it5xwh/stupid_rcu_tricks_corner_case_rcu">评论</a></p>
查看缓存全文
缓存时间:
2026/04/20 14:43
# 愚蠢的RCU技巧:边界情况下的RCU实现
来源:https://people.kernel.org/paulmck/stupid-rcu-tricks-corner-case-rcu-implementations
RCU总是会在意想不到的地方出现。用Fedor Pikus的话来说:“事实上,你可能已经在程序中使用了RCU方法,却完全没有意识到!”(https://cppcon2017.sched.com/event/BgtF/read-copy-update-then-what-rcu-for-non-kernel-programmers)本文将介绍两种非常不正统(有人可能会说“完全不负责任”)的RCU实现类型的使用情况,既有偶然为之的,也有有意识设计的。
## 定时等待RCU
一类非常简单的RCU实现使用固定的时间周期作为宽限期。这显然可以在硬实时内核和应用中很好地工作,尽管它也曾在非实时内核的原型中使用过。在1990年代早期的一次口头讨论中,正是Van Jacobson(https://en.wikipedia.org/wiki/Van_Jacobson)指出,在他正在研究的一个专有UNIX操作系统的研究版本中,15秒的延迟就足够了。我当时口头回应说,在DYNIX/ptx中,中断处理程序有时会执行超过一分钟(即超过15秒),但Jack Slingwine和我想出了一种方法,可以在不需要对读者施加硬实时约束的情况下,获得同样的低开销/高可扩展性效果。Van表示感兴趣,于是我给他寄了第一份RCU会议论文(http://www.rdrop.com/users/paulmck/RCU/rclockpdcsproof.pdf)的早期草稿。几年后,我有幸听到Van对Linux内核RCU赞不绝口。
为了照顾那些像我一样脑海中自带“让它变得合理”过滤器的长期RCU用户,这里给出一个Linux内核中对Van的RCU实现的`synchronize_rcu()`版本:
`void synchronize_rcu(void) { schedule_timeout_uninterruptible(15 * HZ); }`
换句话说,`synchronize_rcu()`执行一个15秒的固定等待,之后在没有证据的情况下假设所有已存在的读者已经完成。
在1990年代中期,Aju John撰写了USENIX论文《动态vnode——设计与实现》(https://www.usenix.org/publications/library/proceedings/neworl/full_papers/john.a),其中提议在一个专有UNIX系统DEC OSF/1 3.0版中,为回收vnode设置固定的10分钟等待时间。
这种方法在硬实时环境中或许确实有意义,但即使在那里也是极其危险的。另一方面,你必须承认,Van和Aju是在采取一种不留余地的性能和可扩展性追求,而且是在非常早的阶段就这么做了!
据我所知,这两种专有UNIX操作系统现已不再投入生产使用,但当年定时等待RCU很可能确实在某些生产环境中使用过。也许它至今仍在使用,尽管在硬实时环境之外使用定时等待RCU可能非常危险!另一方面,这是硬实时使得某事变得简单的罕见例子,至少只要禁止非实时线程使用RCU读侧临界区即可。
## 固定缓冲区RCU
上一节讨论了完全基于时间的RCU宽限期检测。但最近与Hui Xie的一次讨论让我思考,是否也可以完全基于空间(即地址空间)来实现宽限期检测。这可能吗?
Linux内核地址消毒剂(KASAN)(https://docs.kernel.org/dev-tools/kasan.html)会将新释放的内存块保留在固定大小的隔离区中。这样做的目的是增加在对应内存块尚未被重新分配时发生释放后使用的概率,从而减少如果释放的内存被立即重新分配时可能出现的假阴性数量。
然而,这可以被视为另一种边界情况下的RCU实现,其中“读者”被假定在对应内存块被重新分配之前完成。
而rcutorture也做了类似的事情,它将`rcu_torture_current`此前引用的`RCU_TORTURE_PIPE_LEN`(即10个)内存块的释放延迟。此外,总共有10倍于此的元素数量(即100个)。与KASAN一样,其目的是降低假阴性的发生率,这样,一个极长的RCU读者结合一系列极短的RCU宽限期,每个读者仍有大约99%的概率对过短的宽限期发出诊断。
虽然在这两个例子中,固定缓冲区的目的是进行错误检测,但它们也可以用来创建一个极其有限且非常尖锐的RCU宽限期检测机制。因此,对于与Hui Xie讨论中提出的问题,答案是“可以”!
## 时间与空间及其影响
简而言之,一方面仅基于时间,另一方面仅基于空间来实现RCU宽限期检测是可能的(尽管有风险!)。这很好地衬托了同时使用时间和空间进行底层同步的RCU(https://www.linuxfoundation.org/webinars/unraveling-rcu-usage-mysteries),其中`rcu_read_lock()`、`rcu_read_unlock()`、`synchronize_rcu()`和`call_rcu()`处理时间,而`rcu_assign_pointer()`和`rcu_dereference()`处理空间。
谁知道呢?也许有人正在生产环境中使用固定缓冲区RCU。但如果你就是那个人,请理解你需要严格且确定地约束读者持续时间和更新速率。否则,你(以及你的用户!)就需要容忍一定概率的内存损坏。
一些历史背景由传闻中运行BSD UNIX的1980年代DEC VAX系统提供,该系统管理员选择使用已知有缺陷的补丁,据称可以提供大约15%的性能提升。而代价是每周大约多一次崩溃。当时,一些人认为这是系统管理的极致,而另一些人则认为这完全是鲁莽之举。
一个不那么有意的例子来自Linux内核RCU,就在挂起和休眠被接纳入内核后不久。这些特性引入了CPU热插拔,这悄悄地抵消了我对非CPU热插拔内核测试RCU的努力。这意味着rcutorture未能发现我注入RCU的一个愚蠢错误,该错误在非CPU热插拔内核中导致RCU宽限期变成固定的几毫秒延迟。
而且没有人注意到。
除了一个在两CPU系统上测试了数百个并发内核构建的开发者,他报告了该错误,但当我询问他的.config文件时却消失了。几个月后,Nick Piggin的dentry缓存测试触发了这个错误。Nick发现并修复了我那个愚蠢的错误。然后我修改了rcutorture,使其更加关注被测内核的.config文件,并以更有针对性的方式施加压力。考虑到如今Linux内核测试的激进程度,我怀疑类似的错误今天会被更快地注意到。
所以,一如既往,明智选择!;–)
## 历史
- 添加了`synchronize_rcu()`定时等待实现的示例,以强化这些RCU实现方法的不正统性质。
- 添加了1980年代DEC VAX系统选择性能而非可靠性的传闻。
相似文章
The Old New Thing (Raymond Chen)
Raymond Chen重新审视了一种用于交换相邻内存块的单向旋转算法,解释了其递归方法和性能特性。
X AI KOLs Timeline
对 Linux 内核 epoll 子系统中的一个释放后使用(UAF)漏洞的详细分析,该漏洞通过切换到 RCU 修复,以及作者在现代设备上尝试利用该漏洞失败的经过。
matklad
本文讨论了一个基本的内存安全挑战,涉及带标签的联合体,其中指向一个变体的指针在联合体被覆盖为另一个变体后被使用,导致类型混淆。作者还认为,缓冲区溢出是最容易被利用的内存错误,如果采用更好的数组语法本可以缓解。
The Old New Thing (Raymond Chen)
本文来自微软的《老东西》博客,解释了Windows内核回调函数最佳实践背后的原理,特别是为什么阻塞或等待工作项会违背其目的,并通过一个关于驱动程序导致系统挂起的警示故事来说明。
Lobsters Hottest
This research describes compiler-invented loads, where compiler optimizations create additional memory reads not present in source code, turning seemingly secure code into vulnerable binaries with TOCTOU races. It includes audits across kernels, hypervisors, enclaves, and firmware.