Linux驱动调试:RCU锁与读写锁死锁排查工程实践 - 第1节
1.1 RCU 的核心思想:读写分离与延迟回收
RCU(Read-Copy-Update)是一种针对读多写少场景设计的同步机制,其核心思想是:读者不阻塞,写者通过“复制-更新-延迟回收”完成数据修改。与读写锁不同,RCU 允许读者在写者修改数据的过程中继续访问旧数据,从而消除读端锁竞争。
关键设计目标:读者路径上不产生任何原子操作、内存屏障或锁获取,仅依赖编译器和 CPU 对内存访问顺序的保证。
1.1.1 三阶段操作流程
| 阶段 | 操作 | 说明 |
|---|---|---|
| Read | 通过 rcu_dereference() 获取指针 | 读者进入 RCU 临界区,读取共享数据 |
| Copy | 分配新内存,复制旧数据并修改 | 写者不直接修改原数据,而是创建副本 |
| Update | 使用 rcu_assign_pointer() 更新指针 | 原子切换指针,使后续读者看到新数据 |
| Reclaim | 等待宽限期后释放旧数据 | 确保所有旧读者完成后再回收内存 |
1.2 Grace Period(宽限期)概念
宽限期(Grace Period)是 RCU 实现延迟回收的核心机制。它定义了一个时间窗口:从写者更新指针开始,到所有在更新前进入 RCU 临界区的读者全部退出为止。只有宽限期结束后,写者才能安全释放旧数据。
直观理解:宽限期就像“最后一班地铁”——写者必须等待所有已经上车的读者(旧读者)全部下车后,才能关闭站台(释放旧数据)。
1.2.1 宽限期与读者生命周期的关系
| 时间点 | 事件 | 读者状态 |
|---|---|---|
| T0 | 读者 A 进入 RCU 临界区 | 正在访问旧数据 |
| T1 | 写者执行 rcu_assign_pointer() | 读者 A 仍在临界区 |
| T2 | 读者 B 进入 RCU 临界区 | 读者 B 看到新数据 |
| T3 | 读者 A 退出临界区 | 宽限期开始点 |
| T4 | 所有旧读者退出 | 宽限期结束,可回收 |
1.3 宽限期检测机制
Linux 内核实现了多种宽限期检测机制,以适应不同场景的性能需求。最核心的两种是:经典 RCU(Tree RCU)和可抢占 RCU(Preemptible RCU)。
1.3.1 检测机制对比
| 机制 | 检测方式 | 适用场景 | 延迟 |
|---|---|---|---|
| synchronize_rcu() | 阻塞等待所有 CPU 经历一次 quiescent state | 进程上下文,可睡眠 | 毫秒级 |
| call_rcu() | 注册回调,宽限期结束后异步执行 | 中断/软中断上下文 | 异步,无阻塞 |
| rcu_barrier() | 等待所有已注册的 call_rcu 回调执行完毕 | 模块卸载时确保回收 | 取决于回调数量 |
| get_state_synchronize_rcu() | 获取当前宽限期状态,轮询检测 | 非阻塞场景 | 可控制轮询间隔 |
1.3.2 Quiescent State(静止状态)
Quiescent State 是宽限期检测的基本单元。当 CPU 上没有正在执行的 RCU 读者时,称该 CPU 处于静止状态。典型静止状态包括:
- 进程切换(context switch)
- 用户态执行(不在内核态)
- 空闲循环(idle loop)
- 显式调用 rcu_quiescent_state()
调试陷阱:如果某个 CPU 长时间不进入静止状态(例如在无限循环中持有自旋锁),宽限期将无限延长,导致 synchronize_rcu() 永久阻塞。这是 RCU 死锁的常见原因之一。
1.4 RCU 与读写锁的核心差异
| 特性 | RCU | 读写锁(rwlock) |
|---|---|---|
| 读者开销 | 几乎为零(无原子操作) | 需要获取锁(原子操作 + 内存屏障) |
| 写者阻塞 | 不阻塞读者,但需等待宽限期 | 阻塞所有读者和写者 |
| 并发度 | 读者与写者完全并行 | 写者与读者互斥 |
| 内存回收 | 延迟回收,需宽限期保障 | 写者可直接释放 |
| 适用场景 | 读远多于写,且读者路径要求极低延迟 | 读写比例均衡,或写者需要立即生效 |
工程经验:在驱动调试中,如果发现读路径上存在大量 cacheline bouncing 或锁竞争,优先考虑将读写锁替换为 RCU。但要注意 RCU 的延迟回收特性可能引入内存占用问题,需要结合 call_rcu() 或 kfree_rcu() 合理管理。
1.5 本章小结
- RCU 通过读写分离和延迟回收实现无锁读路径。
- 宽限期(Grace Period)是 RCU 安全回收旧数据的核心保障。
- 宽限期检测依赖 CPU 的 Quiescent State,通过 Tree RCU 层级汇总。
- Linux 提供
synchronize_rcu()、call_rcu()、rcu_barrier()等多种宽限期等待机制。 - RCU 与读写锁的选择取决于读写比例和读者延迟要求。