
编写 Linux 驱动或修改内核代码时锁的选择和上下文的判断是开发者最核心的两个“心法”。如果选错了锁轻则性能下降重则导致死锁或系统崩溃Panic。你可以通过以下这套“分层思考法”来构建开发思维第一步精准识别“上下文” (Context)在 Linux 中代码要么在为“人”进程工作要么在为“事”硬件中断工作。1. 进程上下文 (Process Context)特征由系统调用或内核线程触发。它拥有一个明确的“身份证”——struct task_struct即current指针有效。权利可以睡眠。它可以等待 I/O、等待内存、或者获取一个可能导致阻塞的信号量。如何识别调用!in_interrupt()为真。2. 中断上下文 (Interrupt Context)特征由硬件强行打断 CPU 执行流触发。它没有固定的“身份证”它借用当前被中断进程的堆栈。禁忌绝对不能睡眠。一旦在中断中睡眠调度器无法找回它系统直接卡死。细分硬中断 (Hard IRQ)处理最紧急的硬件信号执行极快。软中断/Tasklet (SoftIRQ/Tasklet)处理稍不紧急的任务虽然也在中断上下文中但允许被硬中断打断。如何识别调用in_interrupt()为真。3. 原子上下文 (Atomic Context)这是一个更广的概念。所有中断上下文都属于原子上下文同时持有自旋锁的进程上下文也进入了原子上下文。在原子上下文中任何可能导致放弃 CPU 的行为如msleep都是禁止的。第二步锁的选择逻辑 (The Locking Strategy)当你确定了上下文就可以按照这个决策树来选择锁情况 A资源在“进程上下文”之间共享场景两个驱动函数都被用户空间的应用程序调用如两个线程同时写/dev/my_dev。推荐mutex(互斥锁)。原因临界区可能很大比如需要拷贝大量数据让后面来的进程睡觉比让 CPU 瞎转自旋更划算。情况 B资源在“进程上下文”与“中断上下文”之间共享场景进程在写缓冲区而中断处理程序ISR在读这个缓冲区。推荐spin_lock_irqsave()。原因中断不能睡所以必须用自旋锁。为了防止当前核的进程拿锁时被本地中断打断导致死锁必须在拿锁的同时关闭本地中断。情况 C资源在“中断上下文”之间共享场景两个不同的中断或者同一个中断在不同 CPU 上同时运行处理共享寄存器。推荐spin_lock()。原因大家都不能睡只能自旋。第三步思考维度总结表可以把这张表印在脑子里保护对象涉及的最高优先级上下文推荐使用的锁本质理由硬中断 (Hard IRQ)spin_lock_irqsave必须防止多核竞争 本地硬中断重入软中断 (SoftIRQ/Tasklet)spin_lock_bh防止多核竞争 本地软中断重入进程 (仅需同步)mutex允许睡眠节省 CPU 功耗只读频繁写很少RCU或rwlock提高读取的并发性能减少缓存颠簸第四步高级进阶思考Cache 与 性能当你习惯了基本的锁就要开始从Cache 一致性的角度优化锁的粒度 (Lock Granularity)不要用一把大锁保护整个驱动。尝试把数据结构拆分每个结构体用自己的小锁。这样不同 CPU 访问不同结构时Cache Line 不会互相冲突。避免虚假共享 (False Sharing)如果两个锁在物理内存上挨得太近在同一个 64 字节的 Cache Line 里即使它们保护不同的数据多核竞争时也会触发大量的 MESI 缓存失效。解决使用____cacheline_aligned宏让锁在内存中对齐。尽量使用 Per-CPU 变量如果数据可以按 CPU 分开比如统计每个核处理了多少包就完全不需要锁。每个 CPU 只改自己的 Cache效率达到最高。总结你的开发检查清单这个函数会在中断里跑吗会只能用自旋锁且不能有任何睡眠函数。锁的临界区里有 I/O 或内存申请吗有考虑用mutex并确保不会在中断里调用。这个变量被访问的频率极高吗是考虑RCU或者将变量Per-CPU化避开昂贵的原子指令。