2. 上半部与下半部:上半部(硬中断)的紧急任务、下半部的延迟处理需求、为什么需要下半部机制
2.1 上半部(硬中断)的紧急任务
当硬件设备产生中断信号时,CPU 会立即暂停当前执行的进程,跳转到中断服务例程(ISR,Interrupt Service Routine)执行。这个阶段被称为上半部(Top Half),其核心特征是原子性与紧迫性。
上半部必须完成以下不可延迟的任务:
- 保存硬件状态:读取设备的状态寄存器,确认中断源,防止中断丢失。
- 清除中断标志:向设备发送 ACK 信号,解除硬件中断请求线,允许后续中断到达。
- 拷贝关键数据:从硬件 FIFO 或 DMA 缓冲区中快速搬移少量时间敏感数据(如网卡接收到的第一个数据包描述符)。
- 标记中断发生:设置一个软件标志位或唤醒一个内核线程,通知下半部有工作待处理。
关键约束:上半部执行期间,当前 CPU 上的中断是关闭的(或至少当前中断线被屏蔽)。这意味着上半部代码必须短小精悍,通常只占用几十到几百微秒。任何耗时操作(如复杂的协议解析、磁盘 I/O、内存分配)都严禁出现在上半部中。
2.2 下半部的延迟处理需求
与上半部的“立即响应”相反,下半部(Bottom Half)负责处理那些可以推迟但必须最终完成的工作。下半部在中断使能的环境下执行,允许被更高优先级的中断打断。
典型的下半部处理内容包括:
| 处理类别 | 具体任务 | 延迟容忍度 |
|---|---|---|
| 数据协议处理 | TCP/IP 协议栈解析、重组、校验和计算 | 毫秒级 |
| 驱动后处理 | 更新软件统计计数器、管理环形缓冲区指针 | 毫秒级 |
| 唤醒等待进程 | 唤醒因等待 I/O 而睡眠的用户进程 | 微秒~毫秒级 |
| 内存管理操作 | 释放 skb(socket buffer)、调整内存池水位 | 毫秒级 |
下半部的设计哲学是:将非紧急但繁重的计算任务从中断上下文中剥离出来,放到一个更宽松的执行环境中运行,从而大幅降低系统关中断时间,提升实时性和吞吐量。
2.3 为什么需要下半部机制
如果没有下半部机制,所有中断处理都必须在关中断的上半部中完成,系统将面临以下严重问题:
- 中断延迟不可控:长时间关中断会导致高优先级中断(如时钟中断)被阻塞,系统时间精度下降,甚至丢失关键事件。
- CPU 利用率低下:上半部不能睡眠、不能调度,只能忙等或轮询,浪费大量 CPU 周期。
- 嵌套中断风险:若在上半部中开启中断以允许嵌套,则复杂的嵌套逻辑极易导致栈溢出或竞态条件。
- 缺乏优先级管理:所有上半部任务优先级相同,无法区分“紧急硬件响应”和“普通数据处理”。
下半部机制通过将中断处理拆分为两个阶段,完美解决了上述矛盾:
核心收益:
- 上半部快速关闭中断 → 降低硬件响应延迟。
- 下半部在开中断环境下执行 → 允许中断嵌套,保证高优先级事件及时响应。
- 下半部可被调度、可睡眠(如工作队列) → 支持复杂操作,避免 CPU 空转。
- 提供多种下半部实现(tasklet、软中断、工作队列) → 按需选择实时性与吞吐量的平衡点。
2.4 上半部与下半部的协作流程
下图展示了从硬件中断触发到下半部执行的完整生命周期:
2.5 上半部 vs 下半部:关键差异对比
| 对比维度 | 上半部(硬中断) | 下半部(软中断/tasklet/工作队列) |
|---|---|---|
| 执行上下文 | 中断上下文(不可睡眠) | 软中断上下文 / 进程上下文(可睡眠) |
| 中断状态 | 关中断(当前 CPU) | 开中断(允许嵌套) |
| 执行时机 | 硬件中断触发后立即执行 | 上半部退出后,由内核调度执行 |
| 典型耗时 | 微秒级(< 100 µs) | 毫秒级(可长达数毫秒) |
| 可延迟性 | 不可延迟,必须立即响应 | 可延迟,由内核决定执行时机 |
| 实现机制 | 注册 IRQ handler | tasklet / softirq / workqueue |
2.6 下半部机制的演进与选择
Linux 内核提供了多种下半部实现机制,开发者可根据场景选择:
- 软中断(SoftIRQ):静态分配,性能最高,用于网络 RX/TX、定时器等高频路径。
- tasklet:基于软中断的动态实现,同一 tasklet 串行执行,适合驱动开发者使用。
- 工作队列(Workqueue):运行在进程上下文,可睡眠,适合需要持有锁或执行文件系统操作的任务。