4. 并发控制与锁机制:高频问题、回答框架、公司侧重点
并发问题是 Linux 驱动开发的核心难点,面试官通常通过锁机制考察候选人对中断上下文、临界区保护、性能与正确性权衡的理解。本章梳理最常考的 3 个问题与 1 个真实项目案例,帮你建立“问题 -> 锁选择 -> 实现 -> 调试”的完整回答链路。
面试问答卡片
问题1:在自旋锁和互斥锁之间如何选择?请举例说明。
面试官考察点:对锁适用场景的判断力,是否理解中断上下文与进程上下文的区别,以及能否根据临界区执行时长做取舍。
回答框架:先定性(自旋锁忙等,互斥锁阻塞)→ 对比条件(中断上下文 vs 进程上下文,短临界区 vs 长临界区)→ 举例驱动应用(如 ISR 中保护共享数据只能用自旋锁,设备文件读写用互斥锁)。
示例回答:“自旋锁忙等待,适合中断上下文或极短临界区(<1ms);互斥锁使进程休眠,适合较长临界区。例如 GPIO 中断处理函数中用 spin_lock_irqsave,而字符设备 write 方法中用 mutex_lock。”
可能追问:① 自旋锁在 UP 系统中如何工作?② 怎样避免自旋锁导致死锁?③ 读写锁与自旋锁的关联?
避坑提醒:不要只说“自旋锁快”,要解释“CPU 空转”的开销;避免在可能睡眠的代码中误用自旋锁。
问题2:请说明 spin_lock_irqsave() 与 spin_lock() 的区别,以及何时必须使用前者。
面试官考察点:对中断禁用机制的掌握程度,是否了解关中断的开销以及如何避免死锁。
回答框架:区别(spin_lock_irqsave 保存 + 关闭本地中断)→ 适用场景(ISR 与非 ISR 共享数据)→ 强调保存 flags 为正确嵌套解锁。
示例回答:“spin_lock_irqsave 先保存 EFLAGS 寄存器再关本地中断,防止 ISR 抢占后死锁。例如中断处理函数与 tasklet 共享变量时,必须用 irqsave 变体。而 tasklet 内只被同 CPU 执行,用 spin_lock 即可。”
可能追问:① 为什么不使用 spin_lock_irq?② 多个中断嵌套时 flags 如何管理?③ spin_lock_bh() 与 irqsave 区别?
避坑提醒:一定要说“保存 flags 且解锁时传回”,否则面试官会认为你不知道嵌套解锁。
问题3:出现死锁后,你用哪些方法定位与修复?
面试官考察点:调试经验、内核死锁检测工具(lockdep、hung task)的掌握程度,以及能否从代码层面预防死锁。
回答框架:先排查锁顺序(加锁顺序一致原则)→ 使用 lockdep 打开内核选项 → 查看 /proc/lock_stat → 修复(调整顺序、使用 trylock、减少锁持有的临界区)。
示例回答:“先确认所有加锁路径顺序一致,再开启 CONFIG_PROVE_LOCKING 跑测试,lockdep 会打印可能死锁链。例如之前驱动中 tasklet 与进程上下文顺序相反,调整为统一顺序并加 spin_trylock 兜底解决。”
可能追问:① lockdep 如何检测 AA/ABBA 死锁?② 如何降低锁的粒度?③ 无锁编程(如 RCU)何时可用?
避坑提醒:不要只说“用 lockdep”,要具体说打开哪个内核选项、看哪个日志文件;否则显得空洞。
项目案例:多核网络驱动共享统计计数器
| 维度 | 内容 |
|---|---|
| 项目名称 | 多队列网卡驱动收发统计模块 |
| 业务背景 | 千兆网卡,8 个接收队列,每队列需记录收包数、丢包数、字节数,CPU 核心间共享用于调试与告警。 |
| 规模指标 | 驱动单核约 10 万 pps,8 队列并发 80 万 pps;统计更新在每个中断或 NAPI 轮询中执行。 |
| 技术栈 | Linux 4.19,x86_64;使用 per-CPU 变量 + atomic64 做最终汇总;避免锁争用。 |
| 核心难点 | 中断上下文更新不能睡眠;统计更新频率高,自旋锁导致性能下降 30%。 |
| 面试高频追问 | ① 为什么不用自旋锁而是 per-CPU?② 原子操作在 64 位系统上的开销?③ 如何保证最终汇总一致性?④ 是否考虑 seqcount 或 RCU? |
| 可迁移回答话术 | “高并发统计场景优先 per-CPU 变量,每 CPU 在自己的缓存行中更新,汇总时用 atomic64_add 或关中断后遍历。如果必须加锁,优先选择读写锁并保持临界区极短,同时打开 lockdep 验证无死锁。” |
面试加分点:提到 per-CPU 变量可以避免伪共享(cache line bouncing),并知道对齐到 __cacheline_aligned 宏;能够说出 atomic64_t 在 x86 上使用 LOCK CMPXCHG16B 指令。
常见误区:千万不要说“per-CPU 不需要同步”——每个 CPU 上的代码仍可能被中断抢占,必要时要在 local 访问时禁止本地中断。