3. 中断上下文定义:什么是中断上下文、中断上半部与下半部、中断上下文中的锁约束

在 Linux 内核驱动开发中,中断上下文是理解锁机制与死锁问题的核心概念。本章将系统性地定义中断上下文,剖析上半部与下半部的分工,并明确在中断上下文中使用自旋锁时必须遵守的约束条件。

3.1 什么是中断上下文

中断上下文是指 CPU 在执行中断服务例程(ISR)时所处的特殊执行环境。与进程上下文不同,中断上下文不属于任何进程,没有进程控制块(task_struct)关联,也不能主动睡眠或调度。

对比维度 进程上下文 中断上下文
关联进程 有,current 指针有效 无,current 指向被中断进程
可睡眠 是(通过 schedule()) 否(禁止睡眠)
可被抢占 是(内核抢占开启时) 否(除非嵌套中断)
锁约束 可使用 mutex/semaphore 只能使用 spinlock 且需关中断
典型场景 系统调用、文件操作 硬件中断、软中断、tasklet
关键判断:在驱动代码中,通过 in_interrupt() 宏可以检测当前是否处于中断上下文。若返回非零值,则必须使用自旋锁且不能调用任何可能睡眠的函数。

3.2 中断上半部与下半部

Linux 将中断处理拆分为两个阶段,以平衡实时性与吞吐量:

  • 上半部(Top Half):直接注册为 IRQ 处理函数,执行时间极短(通常微秒级),只完成最紧急的硬件操作(如清除中断标志、拷贝寄存器数据)。
  • 下半部(Bottom Half):推迟执行的剩余工作,可被调度且允许被新中断打断。常见机制包括软中断(SoftIRQ)、tasklet、工作队列(workqueue)。
上半部 vs 下半部速查表
特性 上半部 下半部
执行时机 中断触发后立即执行 上半部返回后,由内核调度
中断屏蔽 默认关闭本地 CPU 中断 软中断/tasklet 可被新中断打断
执行时间 极短(< 100 μs) 相对较长(ms 级)
可睡眠 否 tasklet 不可;workqueue 可
锁类型 spinlock_irqsave spinlock_bh 或 spinlock
中断上下文分层与执行流 中断上下文分层与执行流 硬件中断触发 上半部(Top Half) 关中断 · 快速处理 · 记录数据 软中断 / tasklet 不可睡眠 · 可被新中断打断 工作队列 可睡眠 · 进程上下文 下半部处理完成 注意:上半部必须关中断执行;下半部中 tasklet 仍处于软中断上下文,不可睡眠

3.3 中断上下文中的锁约束

在中断上下文中使用自旋锁,必须遵循以下三条硬性约束,否则将直接导致死锁或数据损坏:

  1. 禁止睡眠:中断上下文不能调用 schedule()、wait_event()、mutex_lock() 等可能睡眠的函数。自旋锁的“自旋等待”本质是忙等,不涉及睡眠,因此是唯一可用的锁类型。
  2. 关中断保护:若中断处理函数与进程上下文共享同一自旋锁,必须在获取锁前关闭本地 CPU 中断。否则,当进程持有锁时发生中断,中断处理函数尝试获取同一锁,将导致单 CPU 死锁。
  3. 下半部锁变体:在下半部(软中断/tasklet)中访问共享数据时,应使用 spin_lock_bh(),该函数在获取锁的同时禁止软中断,防止下半部与自身或进程上下文形成死锁。
死锁风险场景:进程上下文持有锁 A,此时发生中断,中断处理函数尝试获取锁 A。由于进程上下文不会主动释放锁,中断处理函数将永远自旋等待——这就是经典的中断上下文死锁。解决方案:在进程上下文中使用 spin_lock_irqsave() 保存并关闭中断。
中断上下文死锁场景与正确锁使用对比 中断上下文死锁场景与正确锁使用对比 ❌ 死锁场景 进程上下文 持有锁 A(未关中断) 中断触发 中断处理函数 尝试获取锁 A → 死锁 ✅ 正确做法 进程上下文 spin_lock_irqsave(锁 A) 中断被屏蔽 中断处理函数 不会执行,锁安全 核心原则:中断上下文与进程上下文共享锁时,必须使用 spin_lock_irqsave / spin_unlock_irqrestore

3.4 锁选择速查表

根据上下文类型,选择正确的锁变体是避免死锁的第一步:

上下文组合 推荐锁变体 说明
进程上下文 vs 进程上下文 spin_lock() / mutex_lock() 可睡眠时优先使用 mutex
进程上下文 vs 上半部 spin_lock_irqsave() 必须关本地中断,防止死锁
进程上下文 vs 下半部 spin_lock_bh() 关软中断,避免与 tasklet 死锁
上半部 vs 上半部 spin_lock() 同一 CPU 不会嵌套,不同 CPU 安全
下半部 vs 下半部 spin_lock_bh() 防止同一软中断实例重入
实践建议:当不确定中断上下文是否会与当前路径冲突时,优先使用 spin_lock_irqsave()。虽然会略微增加中断延迟,但能从根本上避免死锁风险。在性能敏感路径中,再通过代码审查逐步降级为 spin_lock_bh() 或 spin_lock()。