4. 传统驱动中断模型
在标准 Linux 内核中,中断处理被明确划分为两个阶段:硬中断处理程序(Top Half)和软中断/底半部(Bottom Half)。这种分层设计是为了在中断响应速度和系统吞吐量之间取得平衡。理解这一经典模型,是后续分析中断线程化对驱动兼容性影响的基础。
核心矛盾:硬中断必须极快执行(微秒级),否则会阻塞其他中断或导致 CPU 被锁死;而驱动往往需要完成耗时操作(数据拷贝、协议解析、硬件复位)。Bottom Half 正是为解决这一矛盾而引入的延迟执行机制。
4.1 硬中断处理程序(Top Half)
当硬件设备产生中断信号时,CPU 会立即跳转到内核预注册的中断服务例程(ISR)。这部分代码运行在中断上下文中,具有以下严格约束:
- 不可睡眠:中断上下文没有进程调度实体,不能调用
schedule()、wait_event()或mutex_lock()。 - 不可重入:同一中断线在处理完成前默认被屏蔽,防止嵌套。
- 执行时间极短:通常只完成寄存器存取、状态清除、数据 FIFO 搬运等原子操作。
- 标记底半部:通过
tasklet_schedule()、raise_softirq()或工作队列触发后续处理。
典型 Top Half 代码骨架:
static irqreturn_t my_device_isr(int irq, void *dev_id)
{
struct my_dev *dev = (struct my_dev *)dev_id;
/* 1. 读取中断状态寄存器,确认中断源 */
u32 status = ioread32(dev->regs + INT_STATUS);
/* 2. 清除硬件中断标志,防止重复触发 */
iowrite32(status, dev->regs + INT_CLEAR);
/* 3. 将关键数据从硬件 FIFO 搬运到内存缓冲区 */
dev->rx_count = ioread32(dev->regs + RX_FIFO_COUNT);
memcpy_fromio(dev->rx_buf, dev->regs + RX_FIFO, dev->rx_count);
/* 4. 调度底半部处理(tasklet 示例) */
tasklet_schedule(&dev->rx_tasklet);
return IRQ_HANDLED;
}
4.2 底半部机制(Bottom Half)
Bottom Half 负责执行 Top Half 推迟下来的、相对耗时的处理逻辑。Linux 内核提供了多种底半部实现机制,它们在实时性、执行上下文和调度开销上各有差异:
| 机制 | 执行上下文 | 可睡眠 | 实时性 | 典型用途 |
|---|---|---|---|---|
| 软中断(SoftIRQ) | 软中断上下文 | 否 | 高(内核自身使用) | 网络 RX/TX、块设备、定时器 |
| Tasklet | 软中断上下文 | 否 | 中(基于 SoftIRQ) | 大多数驱动底半部 |
| 工作队列(Workqueue) | 进程上下文 | 是 | 低(依赖调度器) | 需要睡眠的复杂操作 |
| 线程化 IRQ(RT) | 进程上下文(实时线程) | 是 | 高(RT 优先级) | PREEMPT_RT 下的标准方式 |
关键区别:软中断和 Tasklet 运行在中断上下文中,仍然不能睡眠;工作队列运行在进程上下文,可以睡眠但调度延迟不可控。PREEMPT_RT 内核通过中断线程化将 Top Half 也变为可睡眠的实时线程,从而彻底改变了这一模型。
4.3 传统中断处理流程
下图展示了从硬件中断触发到 Top Half 执行、再到 Bottom Half 调度的完整数据流:
4.4 传统模型的实时性缺陷
尽管 Top Half / Bottom Half 模型在通用 Linux 中运行良好,但从实时系统角度看,它存在三个根本性问题:
- 中断不可抢占:Top Half 运行时,本地 CPU 中断被关闭或优先级被提升,任何高优先级实时任务都无法执行,造成优先级反转。
- 软中断优先级固定:SoftIRQ 和 Tasklet 的优先级高于所有用户态进程,即使实时线程优先级更高,也无法抢占正在执行的底半部。
- 非确定性延迟:中断屏蔽时间、软中断执行时间、工作队列调度延迟均不可预测,导致最坏情况响应时间(WCRT)难以保证。
实时性量化对比:
| 指标 | 标准 Linux | PREEMPT_RT 要求 |
|---|---|---|
| 中断关闭时间 | 几十 ~ 几百微秒 | < 10 微秒 |
| 调度延迟抖动 | 毫秒级 | < 100 微秒 |
| 优先级反转风险 | 高 | 低(通过线程化消除) |
4.5 驱动兼容性隐患预览
传统驱动大量依赖 Top Half 的“不可睡眠”假设和 Tasklet 的“软中断上下文”特性。当内核切换到 PREEMPT_RT 后,中断被线程化,Top Half 变为可睡眠的进程上下文,这会导致以下兼容性问题:
- 自旋锁死锁:驱动在 Top Half 中持有的自旋锁,在线程化后可能被同一线程再次尝试获取(死锁),或与进程上下文中的锁形成反转。
- Tasklet 语义变化:PREEMPT_RT 将 Tasklet 实现在线程上下文中,原本不可睡眠的代码路径突然可以睡眠,但驱动未做相应适配。
- ioremap / 寄存器访问时序:线程化后中断响应延迟增加,某些对时序敏感的硬件寄存器读写可能超时或状态错乱。
- 中断共享与嵌套:传统模型依赖中断屏蔽来保证原子性,线程化后需要改用互斥锁或 RT-mutex,否则导致竞态。
提前警示:本章后续内容将逐一深入这些兼容性陷阱,并提供具体的检测方法和修复策略。如果你正在维护一个尚未适配 PREEMPT_RT 的驱动,请特别关注 Top Half 中使用的同步原语和时序假设。