内核同步机制总览
本文对应 成长路径 高优先级:在驱动与内核模块开发中 正确选用锁,并会用 lockdep 发现死锁与 IRQ 上下文错误。
学习目标
- 按 上下文(进程 / 软中断 / 硬中断)选择锁类型。
- 理解 RCU 读多写少场景与 宽限期。
- 开启 CONFIG_LOCKDEP 解读典型报告。
按上下文选用(总表)
| 机制 | 睡眠 | 硬中断 | 软中断 | 典型用途 |
|---|---|---|---|---|
| spinlock | 否 | 是* | 是* | 极短临界区、per-CPU |
| mutex | 是 | 否 | 否 | 进程上下文长临界 |
| rwsem | 是 | 否 | 否 | 读多写少、可睡眠 |
| rwlock | 否 | 谨慎 | 谨慎 | 旧代码,倾向 rwsem |
| RCU | 读侧无锁 | 特殊 | 是 | 读极多、写少 |
| seqlock | 读无锁写加锁 | 部分 | 部分 | jiffies、简单计数 |
* 需使用 spin_lock_irqsave 等正确变体,且临界区 极短。
spinlock 要点
spinlock_t lock;
spin_lock_irqsave(&lock, flags);
/* 临界区 */
spin_unlock_irqrestore(&lock, flags);- 不可 在持 spinlock 时睡眠(含
copy_from_user可能 fault)。 - 死锁:AB-BA、IRQ 嵌套同锁 → 用 lockdep。
mutex 要点
mutex_lock(&dev->lock);
/* 可调度、可访问用户内存 */
mutex_unlock(&dev->lock);适合 字符设备 file_operations 中较长路径;与 interrupt 不要交叉持锁。
RCU 要点
- 读侧:
rcu_read_lock()/rcu_read_unlock(),无原子阻塞写者。 - 写侧:分配新结构 →
rcu_assign_pointer→synchronize_rcu()或call_rcu释放旧数据。 - 网络栈、路由表、大量 只读遍历 使用 RCU。
lockdep 入门
内核配置 CONFIG_LOCKDEP、CONFIG_PROVE_LOCKING。
触发后 dmesg 可能出现:
possible circular locking dependency detected阅读顺序:
- 锁链 A → B → C
- 上下文 hardirq/softirq/process
- 修改:统一加锁顺序、缩短临界区、换 mutex 或 RCU
用户态可配合 CONFIG_DEBUG_ATOMIC_SLEEP 查非法睡眠。
与无锁、DPDK 的边界
| 环境 | 建议 |
|---|---|
| 内核驱动 | 以上锁 + RCU;避免用户态无锁套路 |
| DPDK 数据面 | rte_ring、per-lcore 变量,见 无锁编程 |
| 跨核共享计数 | 内核 atomic_t;DPDK rte_atomic |
实践清单
- 在模块里故意用错上下文(仅 QEMU)观察 lockdep 报告
- 画一张驱动里 所有锁 的获取顺序图
- 对照 Linux 内核模块开发实战 检查 probe/remove 对称性