4. 传统驱动中断模型:硬中断处理程序(top half)与软中断(bottom half)的工作机制

在标准 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 调度的完整数据流:

传统中断处理流程:Top Half 与 Bottom Half 传统中断处理流程(Top Half → Bottom Half) 硬件中断触发 Top Half(硬中断处理程序) 读取状态、清除中断、搬运关键数据 选择 底半部机制 Tasklet 软中断上下文,不可睡眠 工作队列 进程上下文,可睡眠 软中断(SoftIRQ) 内核内部使用,高实时性 所有底半部执行完毕后,CPU 恢复被中断的进程 或进入空闲状态等待下一个中断

4.4 传统模型的实时性缺陷

尽管 Top Half / Bottom Half 模型在通用 Linux 中运行良好,但从实时系统角度看,它存在三个根本性问题:

  1. 中断不可抢占:Top Half 运行时,本地 CPU 中断被关闭或优先级被提升,任何高优先级实时任务都无法执行,造成优先级反转。
  2. 软中断优先级固定:SoftIRQ 和 Tasklet 的优先级高于所有用户态进程,即使实时线程优先级更高,也无法抢占正在执行的底半部。
  3. 非确定性延迟:中断屏蔽时间、软中断执行时间、工作队列调度延迟均不可预测,导致最坏情况响应时间(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 中使用的同步原语和时序假设。