3. 中断上半部与下半部:职责划分与机制对比

在 Linux 中断处理中,将处理逻辑拆分为上半部(Top Half)和下半部(Bottom Half)是保证系统实时性与吞吐量的核心设计模式。上半部负责紧急、简短的操作,下半部负责耗时、可延后的处理。本章节深入剖析上半部的职责、下半部的必要性,并系统对比软中断、tasklet 与工作队列三种下半部实现机制。

3.1 上半部职责:快速响应与最小化关中断时间

中断上半部(也称为硬中断处理程序)在中断上下文(Interrupt Context)中执行,其核心原则是“快进快出”。

  • 保存关键硬件状态:从硬件寄存器中读取中断状态、数据标志或错误码,防止被后续中断覆盖。
  • 清除中断标志:向硬件写入确认(ACK)或清除挂起位,使中断控制器可以接收新的中断请求。
  • 唤醒或调度下半部:通过标记软中断、调度 tasklet 或触发工作队列,将主要数据处理逻辑移交给下半部。
  • 绝对禁止阻塞:上半部不能调用可能导致睡眠的函数(如 copy_to_user、kmalloc(GFP_KERNEL)、mutex_lock)。
关键约束: 上半部执行期间,当前 CPU 的中断被关闭(或至少当前中断线被屏蔽)。执行时间过长会直接导致系统延迟增大、丢帧甚至 watchdog 超时。通常建议上半部执行时间控制在微秒级。
中断上半部职责与下半部调度流程 硬件中断触发 上半部处理 保存状态 · 清除标志 调度下半部 tasklet / 工作队列 下半部执行 可延迟 · 可阻塞(工作队列) ⚠ 设计要点: 上半部必须非阻塞、不可睡眠;下半部根据机制不同,可运行于软中断上下文或进程上下文。

3.2 下半部的必要性:为什么不能全部在上半部完成?

将大量处理逻辑放在上半部会带来严重的系统问题,下半部的引入正是为了解决这些矛盾。

问题 上半部直接处理的影响 下半部引入后的改善
中断延迟增大 长时间关中断,其他中断(包括时钟中断)无法响应 上半部快速退出,中断重新使能,系统保持响应
实时性恶化 高优先级任务被阻塞,无法抢占 下半部可被更高优先级任务或中断抢占
无法使用阻塞 API 中断上下文不能睡眠,无法完成复杂 I/O 工作队列运行于进程上下文,允许睡眠和锁等待
CPU 利用率失衡 单核长时间陷入中断上下文,用户态进程饥饿 下半部可分散到多核或延迟执行,平衡负载
实践警告: 即使使用下半部,也必须控制下半部的执行时间。对于软中断和 tasklet,它们仍然运行在中断上下文中(软中断上下文),不能睡眠。只有工作队列可以安全调用阻塞函数。

3.3 下半部实现机制对比:软中断、tasklet 与工作队列

Linux 内核提供了三种主要的下半部机制,它们在执行上下文、可延迟性、并发模型和适用场景上存在显著差异。

3.3.1 软中断(Softirq)

  • 执行上下文:软中断上下文(可看作中断上下文的一种延伸),不可睡眠。
  • 并发性:同一类型的软中断可以在多个 CPU 上同时运行,必须使用 per-CPU 变量或自旋锁保护数据。
  • 注册方式:静态分配,在编译时通过 open_softirq() 注册,运行时通过 raise_softirq() 触发。
  • 典型用途:网络收发、块设备层、高频率定时器。

3.3.2 tasklet

  • 执行上下文:软中断上下文,不可睡眠。
  • 并发性:同一 tasklet 在同一时刻只能在一个 CPU 上执行,不同 tasklet 可以并行。串行化特性简化了数据保护。
  • 注册方式:动态或静态初始化,使用 DECLARE_TASKLET 或 tasklet_init(),通过 tasklet_schedule() 调度。
  • 典型用途:字符设备驱动、DMA 完成处理、网络协议栈中的辅助处理。

3.3.3 工作队列(Workqueue)

  • 执行上下文:进程上下文(内核线程),可以睡眠,可以使用信号量、mutex、copy_to_user。
  • 并发性:由内核工作线程池管理,默认多线程并行,但可通过配置限制并发度。
  • 注册方式:使用 INIT_WORK 或 INIT_DELAYED_WORK,通过 schedule_work() 或 queue_work() 提交。
  • 典型用途:需要阻塞操作的复杂处理、文件系统操作、内存分配、用户空间通信。
下半部机制对比:软中断 / tasklet / 工作队列 中断上半部 软中断 (Softirq) 不可睡眠 · 多核并行 tasklet 不可睡眠 · 串行化 工作队列 (Workqueue) 可睡眠 · 进程上下文 机制特性对比 • 软中断:最高优先级下半部,适合高频、短小、可重入的任务(如网络 RX/TX) • tasklet:基于软中断实现,自动串行化,适合驱动中大多数下半部场景 • 工作队列:运行于内核线程,支持阻塞操作,适合复杂、低频率的后台处理 • 选择原则:能否睡眠?→ 能则用工作队列;否则用 tasklet(优先)或软中断

3.4 综合对比表:快速决策参考

维度 软中断 tasklet 工作队列
执行上下文 软中断上下文 软中断上下文 进程上下文
是否可睡眠 否 否 是
并发模型 多核并行(同类型) 串行化(同类型) 多线程并行
调度延迟 极低(立即执行) 低(下一个软中断检查点) 中等(依赖内核线程调度)
数据保护复杂度 高(需 per-CPU 或锁) 低(自动串行化) 中(可使用 mutex)
典型应用 网络协议栈、块层 驱动中断下半部、DMA 完成 文件系统、用户态通信
设计建议: 对于驱动开发者,tasklet 是中断下半部的首选默认机制。当需要处理 DMA 数据拷贝到用户空间、或需要等待硬件操作完成时,才升级为工作队列。软中断通常留给内核子系统使用,驱动中直接注册软中断的情况较少。