Linux 内核 RCU 补丁评审清单:17 条规则的完整解读与源码实现剖析
2026/9/7 17:01:26 网站建设 项目流程

Linux 内核 RCU 补丁评审清单:17 条规则的完整解读与源码实现剖析

【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux

本文系统讲解 Linux 内核官方文档 Documentation/RCU/checklist.rst 中"RCU 补丁制作与评审检查清单"的全部 17 条规则,并结合当前仓库中 include/linux/rcupdate.h、include/linux/rculist.h 等核心头文件的真实实现,深入剖析每条规则背后的内存屏障机制、flavor 配对关系、回调执行模型与调试手段。读完本文,你将能够在自己编写或评审任何使用 RCU(Read-Copy-Update)的补丁时,完整覆盖读侧临界区、更新侧互斥、内存排序、回调约束与模块卸载屏障等全部关键点,避免与遗漏锁原语同等级别的并发缺陷。

这份清单并非凭空产生,文档开宗明义地说明:违反其中任何一条规则,都会造成"与遗漏一个锁原语同类的后果";而这份清单本身也是维护者长期评审 RCU 补丁经验的结晶(见 checklist.rst 第 8-12 行)。

规则 0:先回答"是不是读多写少"

在动手用 RCU 之前,第一个问题永远是:该数据结构是否处于读多写少(read-mostly)的场景?文档给出的经验阈值非常具体——如果数据结构超过约 10% 的时间都在被更新,就应该强烈考虑其他方案,除非详细的性能测量证明 RCU 仍是正确选择。

这背后是 RCU 的基本代价模型:RCU 通过增加写侧开销来降低读侧开销。读侧几乎零成本(在经典 RCU 下读侧临界区只是禁止抢占/软中断),但更新侧必须等待宽限期(grace period)才能回收旧数据。因此正常使用 RCU 的代码必然是读远多于写。

文档同时列出了三个例外情形,即使不满足读多写少也可以使用 RCU:

  1. 性能不是问题,RCU 让实现更简单——典型例子是 Linux 2.6 内核中的动态 NMI 代码(在 NMI 很少的架构上);
  2. RCU 读侧原语的极低实时延迟至关重要
  3. 用 RCU 读者防止无锁更新中的 ABA 问题。这种情况会产生一种略显反直觉的现象:rcu_read_lock()/rcu_read_unlock()被用来"保护更新",但它能为某些无锁算法带来与垃圾回收器类似的简化效果。

规则 1:更新侧代码必须有真正的互斥

RCU 只是豁免了读者的锁(读者几乎可以"裸奔"),但写者之间仍然必须有互斥机制。文档给出三种被认可的方案:

  • a. 加锁(locking);
  • b. 原子操作(atomic operations);
  • c. 将更新限制在单一任务中。

选择 b(原子操作)时,你必须在评审中解释如何正确处理弱序机器上的内存屏障——注意"弱序机器"几乎是全部机器,连 x86 都允许后面的 load 重排到前面的 store 之前。选择 c(单任务更新)时,则要说明该单任务为什么不会在大系统上成为瓶颈(例如该任务只是更新关于自身、供其他任务读取的信息,从定义上就不存在瓶颈)。文档还提醒:所谓"大"的定义已经变了,2000 年时 8 个 CPU 就算大,2017 年 100 个 CPU 已不足为奇。

规则 2:读侧临界区必须正确使用 rcu_read_lock() 家族

RCU 读侧临界区(rcu_read_lock()等原语)的作用是阻止宽限期过早结束。否则,你的读侧代码脚下的数据会被"毫不客气地"释放掉——文档原话说这会"大幅增加你的内核的统计风险(actuarial risk)"。

文档给出的经验法则是:任何对 RCU 保护指针的解引用,都必须被以下之一覆盖

  • rcu_read_lock()rcu_read_lock_bh()rcu_read_lock_sched()
  • 或合适的更新侧锁。

另外两个容易被忽视的事实:

  • 显式关闭抢占(如preempt_disable())在效果上等价于rcu_read_lock_sched(),但可读性更差,而且会让 lockdep 无法检测锁问题;
  • 获取一把普通自旋锁(raw spinlock)本身也会进入 RCU 读侧临界区。

guard(rcu) / scoped_guard(rcu):更少出错的新写法

文档特别介绍了guard(rcu)()scoped_guard(rcu)()两个 C++ 风格守卫原语:前者把当前作用域剩余部分标记为 RCU 读侧临界区,后者只标记下一条语句。相比手工成对调用rcu_read_lock()/rcu_read_unlock(),guard 形式不容易漏掉 unlock(例如中途return)。当前仓库源码中已经大量使用这种写法,例如 include/rv/da_monitor.h、include/net/ip_vs.h、include/linux/bpf.h、include/linux/kallsyms.h 中都有guard(rcu)();的实际使用。

两条不可逾越的红线

  1. 不能依赖"本代码只会在不可抢占内核编译"。这类代码在启用了CONFIG_PREEMPT_COUNT=y的内核中会且将会被打破;
  2. RCU 保护指针"泄漏"出读侧临界区,与指针泄漏出锁保护同等恶劣。除非在指针离开临界区之前已经安排了其他保护手段(如加锁或引用计数)。

规则 3:更新代码必须容忍并发读

RCU 的全部意义就在于让读者不带锁、不做原子操作地运行——因此读者必然会在更新进行期间运行。文档按推荐程度列出四种处理方式:

  • a. 使用 RCU 变体的链表/hlist 更新原语list_*_rcu()hlist_*_rcu())在 RCU 保护的链表上做增删替换,或使用内核中其他 RCU 保护的数据结构。文档评价:"这几乎总是最佳方案。"
  • b. 在 a 的基础上,为每个元素维护一把读写双方都获取的元素锁,保护元素内部状态;读者从不访问的字段可以只由更新方获取的另一把锁保护。"效果同样很好。"
  • c. 让更新对读者呈现原子性。例如对对齐字段的指针更新、单个原子原语都是原子的;但在锁保护下的操作序列对 RCU 读者不呈现原子性,多个原子原语的序列也不原子。变通办法是把多个相关字段挪到一个独立结构体中,用一层间接引用(更新指针)解决多字段原子性问题。"可行,但开始有点棘手了。"
  • d. 仔细编排更新与读取的顺序,使读者在更新的每个阶段都看到有效数据。文档直言这"比听起来难得多"——现代 CPU 倾向于重排内存访问,代码里必须大量撒布内存排序操作,导致代码难以理解和测试。可行时使用smp_store_release()/smp_load_acquire()这类成对原语,个别情况需要smp_mb()全屏障。文档再次建议:把变化数据分组到一个独立结构体,通过更新指针使其呈现原子变更,通常优于手工编排。

规则 4:弱序 CPU 上的四项强制措施

几乎所有 CPU 都是弱序的(再次强调:连 x86 允许后面的 load 重排到前面的 store 之前)。RCU 代码必须采取以下全部措施来防止内存破坏:

4a. 读者用 rcu_dereference() 保证取指顺序

rcu_dereference()确保 CPU先取到指针,再取指针指向的数据。文档特别指出这在 Alpha 架构上是"真的需要"的。它还有两个附加价值:

  • 优秀的文档工具:让读代码的人一眼看出哪些指针受 RCU 保护;
  • 防御编译器重排:编译器越来越激进地重排代码,rcu_dereference()也能阻止破坏性的编译器优化。文档同时警告:只要足够"狡黠有创意",误用其返回值也是可能的,详见 rcu_dereference.rst。

从源码看,这一原语的实现印证了文档的描述。include/linux/rcupdate.h 中的__rcu_dereference_check()宏用READ_ONCE(p)读取指针(禁止编译器重排/拆分访问)、通过RCU_LOCKDEP_WARN在 lockdep 下检查是否处于正确的读侧上下文、再通过rcu_check_sparse()执行 sparse 的__rcu空间检查;而最终的rcu_dereference(p)只是rcu_dereference_check(p, 0)的封装(rcupdate.h)。list_for_each_entry_rcu()等所有"_rcu()"链表遍历原语内部都使用了它。注意:更新侧代码使用rcu_dereference()"_rcu()"遍历原语是完全合法的(虽然冗余),对读写共用代码特别有用;但若在 RCU 读侧临界区之外使用,lockdep 会抱怨——处理办法见 lockdep.rst。

4b-4d. 链表/hlist 的 RCU 专用增删替换原语

  • list_add_tail_rcu()/list_add_rcu()插入元素、用hlist_add_head_rcu()插入 hlist 元素,防止弱序机器把"结构体初始化"和"指针种入"乱序
  • list_del_rcu()/hlist_del_rcu()删除元素,防止list_del()的指针投毒(poisoning)对并发读者造成毒性伤害
  • list_replace_rcu()/hlist_replace_rcu()在相应类型的 RCU 保护链表中以新结构替换旧结构;
  • "hlist_nulls" 类型的 RCU 保护链表适用类似的规则(见 rculist_nulls.rst)。

4e. 先初始化,后发布

更新必须确保:结构体的初始化先于指向它的指针被公开。公开一个可被 RCU 读侧临界区遍历的结构体指针时,必须使用rcu_assign_pointer()

源码印证(include/linux/rcupdate.h):rcu_assign_pointer()的核心就是smp_store_release(&p, RCU_INITIALIZER(...))——一个 release 语义的存储。这正好与 4a 中读者侧READ_ONCE()的 acquire 依赖链配合,构成"先看到初始化好的结构体、再看到新指针"的全局顺序。内核注释还特别提醒:在极少数特殊场合可以用RCU_INIT_POINTER()代替(更快,因为不约束 CPU 和编译器),但"该用rcu_assign_pointer()时错用RCU_INIT_POINTER()是一件非常糟糕的事,会导致无法诊断的内存破坏"。

规则 5 与 6:回调与同步原语的执行上下文约束

规则 5:如果使用了call_rcu()call_srcu()call_rcu_tasks()call_rcu_tasks_trace(),回调函数可能从 softirq 上下文被调用,且在任何情况下都处于 bottom half 关闭状态。特别地,回调不能阻塞。如果回调需要阻塞,应该在回调里调度一个 workqueue 处理函数去执行那段代码;对于call_rcu()queue_rcu_work()已经替你做了这件事。

规则 6:因为synchronize_rcu()可能阻塞,它不能在任何一种 irq 上下文中调用。同样的规则适用于synchronize_srcu()synchronize_rcu_expedited()synchronize_srcu_expedited()synchronize_rcu_tasks()synchronize_rcu_tasks_rude()synchronize_rcu_tasks_trace()

关于加急(expedited)变体,文档给出三条明确约束:

  1. 加急版与非加急版语义相同,但加急更消耗 CPU,应仅限于罕见的配置变更操作,且这些操作通常不会在实时负载运行时执行。对 IPI 敏感的实时负载可以用rcupdate.rcu_normal内核启动参数完全禁用加急宽限期(可能有性能影响);
  2. 如果你在循环里反复调用加急原语,请"帮帮大家":重构代码,把更新批量化,让一个非加急原语覆盖整个批次。这很可能比循环调用加急原语更快,而且对系统其他部分(尤其是其他 CPU 上的实时负载)友好得多;或者改用call_rcu()这类异步原语;
  3. 加急版会向其他 CPU 发送 IPI(SRCU 的加急版除外,见规则 13),这是它对实时负载不友好的根本原因。

规则 7:更新侧与读侧原语必须正确配对

自 v4.20 起,一个内核只实现一种 RCU flavorPREEMPTION=n时是 RCU-sched,PREEMPTION=y时是 RCU-preempt。配对规则如下:

  • 更新方使用call_rcu()synchronize_rcu()时,对应的读者可以使用三类原语中的任意一类:
    1. rcu_read_lock()/rcu_read_unlock()
    2. 任何"关闭再重新开启 softirq"的成对原语,例如rcu_read_lock_bh()/rcu_read_unlock_bh()
    3. 任何"关闭再重新开启抢占"的成对原语,例如rcu_read_lock_sched()/rcu_read_unlock_sched()
  • 更新方使用synchronize_srcu()call_srcu()时,读者必须使用srcu_read_lock()/srcu_read_unlock(),且作用于同一个srcu_struct
  • 加急版宽限期等待原语的规则与非加急版完全相同。

RCU Tasks 家族的配对规则:

  • a. 更新方用synchronize_rcu_tasks()call_rcu_tasks()时,读者必须不做自愿上下文切换(即不阻塞);
  • b. 更新方用call_rcu_tasks_trace()synchronize_rcu_tasks_trace()时,读者必须用rcu_read_lock_trace()/rcu_read_unlock_trace()
  • c. 更新方用synchronize_rcu_tasks_rude()时,读者必须使用任何关闭抢占的手段,例如preempt_disable()/preempt_enable()

文档强调:配错原语会导致混乱乃至坏掉的内核,历史上甚至造成过可被利用的安全漏洞。因此使用"不那么显而易见"的配对时,写注释是必须的义务。文档举了一个真实案例:网络中的 XDP 功能从网络驱动的 NAPI(softirq)上下文调用 BPF 程序。BPF 的数据结构重度依赖 RCU 保护,而 BPF 程序的执行完全处于 NAPI poll 周期中一段local_bh_disable()之内——这是安全的,因为"当更新方使用call_rcu()synchronize_rcu()时,读者可以使用任何关闭 BH 的手段"。

规则 8:synchronize_rcu() vs call_rcu() 的选型与自限流

虽然synchronize_rcu()call_rcu()慢,但它通常产生更简单的代码。除非更新性能至关重要、更新方不能阻塞、或synchronize_rcu()的延迟在用户空间可见,否则应优先使用synchronize_rcu()。此外,kfree_rcu()kvfree_rcu()通常比synchronize_rcu()产生更简单的代码,且没有后者毫秒级的多毫秒延迟——请在适用时充分利用它们的"发射后不管"(fire and forget)释放内存能力。

call_rcu()/kfree_rcu()/kvfree_rcu()路线有一个必须人工弥补的性质:synchronize_rcu()自动自限流——宽限期因任何原因延迟时,更新会相应地变慢;而使用call_rcu()的代码如果宽限期延迟却不限制更新速率,可能造成过高的实时延迟甚至 OOM。文档给出四种获得自限流性质的办法:

  • a.计数限流:统计 RCU 保护数据结构中等待宽限期结束的元素数(或只统计等待延迟释放的数量),强制施加上限,必要时让更新阻塞等待先前延迟释放完成。阻塞更新的一种方式是在更新侧拿 mutex(不要用自旋锁——其他 CPU 在锁上自旋可能让宽限期永远无法结束);另一种方式是给内存分配器包一层 wrapper,在等待 RCU 宽限期的内存过多时模拟 OOM。
  • b.限制更新速率:例如更新每小时只发生一次,则无需显式限流。老版本的 dcache 子系统就是这么做的——用一把全局锁保护更新,天然限制了速率。
  • c.可信更新:如果更新只能由超级用户或某个可信用户手动触发,可能无需自动限流("反正超级用户已经有很多办法把机器搞崩了")。
  • d.周期性调用rcu_barrier(),允许每个宽限期只完成有限数量的更新。

同样的告诫适用于call_srcu()call_rcu_tasks()call_rcu_tasks_trace(),这也是为什么分别存在srcu_barrier()rcu_barrier_tasks()rcu_barrier_tasks_trace()。文档还保留了一个冷静的提醒:虽然这些原语会对"单个 CPU 回调堆积过多"的情况采取行动以避免内存耗尽,但一个坚定的用户或管理员仍然可以耗尽内存——尤其是当系统被配置为把所有 RCU 回调卸载(offload)到单个 CPU 上,或系统可用内存本来就很少时。

规则 9 与 10:链表遍历原语的双向规则

规则 9(正向):所有 RCU 链表遍历原语——包括rcu_dereference()list_for_each_entry_rcu()list_for_each_safe_rcu()——必须要么位于 RCU 读侧临界区内,要么被合适的更新侧锁保护。读侧临界区由rcu_read_lock()/rcu_read_unlock()或类似原语(如rcu_read_lock_bh()/rcu_read_unlock_bh())界定;后一种情况下,为了不让 lockdep 抱怨,必须使用与之匹配的rcu_dereference_bh()

为什么允许在持有更新侧锁时使用遍历原语?因为当读者与更新方共用同一段代码时,这能显著减少代码膨胀。该场景的额外原语见 lockdep.rst。

一个例外:如果数据只会被添加到链式结构中、且在任何读者可能访问的时段内从不被删除,则可以用READ_ONCE()代替rcu_dereference(),读侧标记(rcu_read_lock()/rcu_read_unlock())也可以省略。

规则 10(反向):如果你已经处于 RCU 读侧临界区、且没有持有合适的更新侧锁,你必须使用"_rcu()"变体的链表宏。做不到这一点会"弄坏 Alpha、让激进的编译器生成错误代码、并让试图理解你代码的人困惑"。

规则 11 与 12:RCU 回调的锁与执行模型

规则 11:RCU 回调获取的任何锁,必须在别处以 softirq 关闭的方式获取(例如spin_lock_bh())。只要某处获取该锁时没有关闭 softirq,就一定会死锁——当 RCU softirq 处理程序恰好在"那次获取"的临界区内中断并运行你的 RCU 回调时,死锁即刻发生。

规则 12澄清了 RCU 回调执行的三个"不要想当然":

  1. 回调可以且确实并行执行。很多回调只是kfree()的包装,问题不大(内存分配器自己的锁能处理);但如果回调操作共享数据结构,必须使用访问/修改该结构所需的全部同步手段;
  2. 不要假设回调在与call_rcu()相同的 CPU 上执行。例如某 CPU 离线时若还有挂起回调,该回调会在某个存活的 CPU 上执行(否则一个"自我繁殖"的 RCU 回调会永远阻止目标 CPU 离线)。此外,被rcu_nocbs=启动参数指定的 CPU 很可能始终在其他 CPU 上执行回调——对某些实时负载来说,这正是使用rcu_nocbs=的全部目的;
  3. 不要假设按序入队的回调按序被调用,即使它们都排在了同一个 CPU 上;也不要假设同 CPU 的回调串行执行。例如在内核允许某 CPU 在"卸载/不卸载"回调模式间切换期间,该 CPU 的回调可能既被该 CPU 的 softirq 处理程序并发执行、又被该 CPU 的 rcuo kthread 并发执行——此时回调可能并发且乱序地运行。

规则 13:SRCU——可睡眠的读侧,以及它的代价

与大多数 RCU 变体不同,在 SRCU 读侧临界区(srcu_read_lock()/srcu_read_unlock()界定)中阻塞是允许的——这就是 "SRCU" 中 "S"(sleepable)的含义。guard(srcu)()scoped_guard(srcu)形式同样可用,往往更易用。文档告诫:如果读侧临界区不需要睡眠,应该用 RCU 而不是 SRCU,因为 RCU 几乎总是更快、更易用。

SRCU 还有一个与众不同的要求:显式的初始化和清理。可以在编译期用DEFINE_SRCU()DEFINE_STATIC_SRCU()DEFINE_SRCU_FAST()DEFINE_STATIC_SRCU_FAST(),也可以在运行期用init_srcu_struct()/init_srcu_struct_fast()cleanup_srcu_struct()。这些原语接收一个struct srcu_struct,它定义了一个 SRCU 域的作用域。初始化后,这个srcu_struct会传递给srcu_read_lock()srcu_read_unlock()synchronize_srcu()synchronize_srcu_expedited()call_srcu()

这个"域作用域"正是可睡眠读侧能够成立的机制:给定的synchronize_srcu()只等待通过同一个srcu_struct调用的srcu_read_lock()/srcu_read_unlock()所管辖的 SRCU 读侧临界区。一个子系统只会拖延自己的更新,而不是其他使用 SRCU 的子系统的更新——因此 SRCU 比"如果允许 RCU 读侧临界区睡眠的 RCU"更不容易把系统拖进 OOM。

但可睡眠不是免费的,两条代价:

  1. 配对的srcu_read_lock()/srcu_read_unlock()必须传入同一个srcu_struct
  2. 宽限期探测开销只在共享同一个srcu_struct的更新之间摊销,而不是像其他 RCU 形式那样全局摊销。

因此,SRCU 只应在极度读密集的场景、或需要 SRCU 读侧死锁免疫/低读侧实时延迟的场景下,才优先于rw_semaphore。需要轻量级读者时还应考虑percpu_rw_semaphore

此外两个相关事实:SRCU 的加急原语synchronize_srcu_expedited()从不向其他 CPU 发 IPI,因此比synchronize_rcu_expedited()对实时负载更友好;RCU Tasks Trace 读侧临界区(rcu_read_lock_trace()/rcu_read_unlock_trace())中也允许睡眠,但这是专用 flavor,使用前应先咨询其现有用户——多数情况下应改用 SRCU(同样有guard(rcu_tasks_trace)()/scoped_guard(rcu_tasks_trace)可用)。

最后,rcu_assign_pointer()之于 SRCU 与其之于其他 RCU 形式完全一样,但为了避免 lockdep 报错,解引用应该用srcu_dereference()而不是rcu_dereference()

规则 14:破坏性操作之前先断开读者可达的路径

call_rcu()synchronize_rcu()及其同类原语的全部意义,就是等到所有已存在的读者都结束后再执行某个破坏性操作。因此至关重要的一点是:移除任何可能受破坏性操作影响的、读者可能沿用的路径,然后才调用call_rcu()/synchronize_rcu()或同类原语。由于这些原语只等待已存在的读者,保证后续读者能安全执行是调用者的责任。

规则 15:读侧原语本身不含内存屏障

各种 RCU 读侧原语不一定包含内存屏障,因此应假设 CPU 和编译器会自由地把代码重排进/重排出 RCU 读侧临界区——处理这件事是 RCU更新侧原语的职责。对于 SRCU 读者,可以在srcu_read_unlock()之后紧跟smp_mb__after_srcu_read_unlock()来获得完整屏障。

规则 16:用内核自带的调试工具验证 RCU 代码

文档推荐四个验证手段,各自的发现能力如下:

调试手段能发现的问题
CONFIG_PROVE_LOCKING(即 lockdep)对 RCU 保护数据结构的访问是否在正确的 RCU 读侧临界区内、是否持有正确的锁组合,或是否满足其他恰当条件
CONFIG_DEBUG_OBJECTS_RCU_HEAD是否在同一个对象上一次call_rcu()(或同类)调用之后、宽限期尚未结束之前,又把它传给了call_rcu()(或同类)
CONFIG_RCU_STRICT_GRACE_PERIOD配合 KASAN 检查从 RCU 读侧临界区泄漏出去的指针。该选项对性能和可扩展性都很苛刻,因此被限制在四 CPU 系统上使用
__rcusparse 检查给 RCU 保护数据结构的指针标注__rcu,sparse 会在未经任何rcu_dereference()变体服务的情况下访问该指针时发出警告

这些调试工具能帮你找到"否则极其难以发现"的问题。从源码结构看,lockdep 的断言入口如 include/linux/rcupdate.h 中的lockdep_assert_in_rcu_read_lock()/lockdep_assert_in_rcu_read_lock_bh()/lockdep_assert_in_rcu_read_lock_sched()可以直接在代码中埋点,强制检查当前是否处于对应类型的 RCU 读侧临界区;而 sparse 侧的__rcu检查则由rcu_dereference()等宏内部的rcu_check_sparse()(rcupdate.h)在__CHECKER__编译时激活——文档第 4 条规则里"更新侧误用rcu_dereference()会让 lockdep 抱怨"正是这套机制在发挥作用。

规则 17:模块卸载必须等回调,宽限期不够

如果你把一个定义在模块内的回调函数传给了call_rcu()call_srcu()call_rcu_tasks()call_rcu_tasks_trace(),那么在卸载该模块之前,必须等待所有挂起回调被调用

一个极易犯的错误:认为等待一个宽限期就足够了。文档明确否定:不够。例如synchronize_rcu()的实现不保证等待其他 CPU 上通过call_rcu()注册的回调——即便回调就在当前 CPU 上,如果该 CPU 最近离线又上线过,同样不保证。

正确做法是使用对应的 barrier 函数:

  • call_rcu()rcu_barrier()
  • call_srcu()srcu_barrier()
  • call_rcu_tasks()rcu_barrier_tasks()
  • call_rcu_tasks_trace()rcu_barrier_tasks_trace()

而 barrier 函数又不保证等待宽限期:例如当系统中没有任何call_rcu()回调排队时,rcu_barrier()可以且将会立即返回。

因此,如果你需要等宽限期等所有既有回调,就必须两个函数都调用,配对取决于 RCU flavor:

  • synchronize_rcu()synchronize_rcu_expedited(),加上rcu_barrier()
  • synchronize_srcu()synchronize_srcu_expedited(),加上srcu_barrier()
  • synchronize_rcu_tasks()加上rcu_barrier_tasks()
  • synchronize_tasks_trace()加上rcu_barrier_tasks_trace()

必要时可以用 workqueue 之类的机制并发执行这两组函数。更多细节见 rcubarrier.rst。

快速自检表

在实际评审或自查一份 RCU 补丁时,可以按下面的顺序过一遍:

  1. 场景是读多写少(或有文档规则 0 列出的例外理由)?
  2. 更新侧有互斥(锁 / 原子操作 / 单任务)?
  3. 每次解引用 RCU 指针都包在rcu_read_lock()家族 /guard(rcu)()/"_rcu()"遍历或更新侧锁内?
  4. 指针发布用rcu_assign_pointer(),插入用list_*_rcu(),删除用list_del_rcu()/hlist_del_rcu()
  5. 更新方与读者方的原语 flavor 配对正确(含 SRCU 的同一srcu_struct、Tasks 系列的禁抢占约束)?
  6. 回调不阻塞;需要阻塞走 workqueue(queue_rcu_work())?
  7. 加急原语只在罕见配置变更中使用,循环里批量化?
  8. call_rcu()路线时有限流/计数/rcu_barrier()之类的自限流手段?
  9. 回调拿的锁在别处都以spin_lock_bh()方式获取?
  10. 不依赖回调的执行 CPU、调用顺序与串行性?
  11. 破坏性操作前已先切断读者可达路径?
  12. 模块卸载前调用了对应 barrier 函数(而非仅等宽限期)?
  13. 打开CONFIG_PROVE_LOCKINGCONFIG_DEBUG_OBJECTS_RCU_HEAD、(4 CPU 系统上)CONFIG_RCU_STRICT_GRACE_PERIOD+ KASAN 和 sparse__rcu检查跑过验证?

以上 13 项与 Documentation/RCU/checklist.rst 的 17 条规则一一对应,配合 RCU 文档索引 Documentation/RCU/index.rst 中 what is RCU、listRCU、rcu_dereference、lockdep splat 处理 等专题文档,构成了一套从使用决策到调试验证的完整 RCU 工程实践闭环。

【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询