Linux内核实时性补丁驱动中断线程化移植实战 - 第4节
4.1 中断上下文与进程上下文
在理解中断线程化之前,必须厘清 Linux 内核中两种最基本的执行上下文:中断上下文(Interrupt Context)与进程上下文(Process Context)。二者的差异直接决定了中断处理程序的设计约束,也是 PREEMPT_RT 补丁引入中断线程化的根本原因。
4.1.1 上下文定义与核心差异
| 对比维度 | 中断上下文 | 进程上下文 |
|---|---|---|
| 触发方式 | 硬件中断信号(IRQ) | 系统调用、异常、内核线程 |
| 可睡眠 | ❌ 不可睡眠(不能调用 schedule()) | ✅ 可睡眠(可被调度器换出) |
| 可被抢占 | 默认不可抢占(关中断或关抢占) | 可被更高优先级任务抢占 |
| 持有锁类型 | 只能使用 spin_lock(自旋锁) | 可使用 mutex、semaphore 等睡眠锁 |
| current 指针 | 指向被中断的进程(不可信) | 指向当前运行的进程 |
| 执行时间要求 | 极短(μs 级),否则丢中断 | 可较长(ms 级甚至更久) |
4.1.2 中断上下文的内核栈与状态
每个 CPU 在中断发生时,会从进程栈切换到专用的中断栈(Interrupt Stack)。中断栈大小通常为 4KB~16KB,远小于进程栈(通常 8KB~16KB)。这意味着中断处理程序必须严格控制栈使用深度,避免递归或大局部变量。
4.2 中断线程化的动机
传统 Linux 内核的中断处理模型存在一个根本矛盾:实时性要求与中断不可抢占性之间的冲突。高优先级实时任务可能因为一个低优先级设备的中断处理而被阻塞,导致响应延迟不可控。
4.2.1 传统中断模型的实时性缺陷
- 中断优先级高于任何进程:即使实时任务优先级为 99,中断处理程序仍会抢占它。低优先级外设的中断可能延迟高优先级任务的执行。
- 关中断导致抢占失效:中断处理期间(尤其是 spin_lock 持有期间),本地 CPU 关中断或关抢占,实时任务无法被调度。
- 中断嵌套不可控:高优先级中断可以嵌套低优先级中断,导致执行顺序与任务优先级无关,破坏实时调度策略。
- 下半部(softirq/tasklet)仍在软中断上下文:软中断在 do_softirq() 中执行,仍然不可睡眠,且优先级高于普通进程。
4.2.2 中断线程化的核心思想
PREEMPT_RT 补丁引入的中断线程化(Interrupt Threading)将大部分中断处理程序(IRQ handler)从原子中断上下文迁移到内核线程(kthread)中执行。每个 IRQ 可以绑定一个实时优先级的内核线程,由调度器统一管理。
| 特性 | 传统中断模型 | 中断线程化模型 |
|---|---|---|
| 执行上下文 | 中断上下文(原子、不可睡眠) | 进程上下文(可睡眠、可抢占) |
| 优先级管理 | 硬件中断优先级(固定) | 内核线程优先级(可配置,遵循 SCHED_FIFO/RR) |
| 可睡眠 | ❌ | ✅(可使用 mutex、wait_queue) |
| 锁兼容性 | 仅 spin_lock | spin_lock + mutex |
| 实时性控制 | 不可控(中断优先级高于一切) | 可控(调度器按优先级调度) |
| 中断延迟 | 低(直接硬件响应) | 略高(线程调度开销) |
4.3 软中断与 tasklet 的线程化改造
软中断(softirq)和 tasklet 是 Linux 内核传统的下半部机制。在 PREEMPT_RT 中,它们被进一步线程化,以消除软中断上下文中的不可抢占性。
4.3.1 软中断的传统实现
软中断在 do_softirq() 中执行,通常由 irq_exit() 在中断返回时调用。软中断运行在软中断上下文(softirq context)中,仍然不可睡眠,且优先级高于普通进程。
// 传统软中断执行路径(非 RT 内核)
irq_handler() → irq_exit() → do_softirq() → 执行软中断向量
// 软中断向量包括:NET_TX_SOFTIRQ, NET_RX_SOFTIRQ, TASKLET_SOFTIRQ 等
4.3.2 PREEMPT_RT 的软中断线程化
PREEMPT_RT 补丁将软中断的执行从 do_softirq() 迁移到每个 CPU 的软中断内核线程(ksoftirqd)中。ksoftirqd 是一个 SCHED_FIFO 优先级为 1 的内核线程,可以被更高优先级的实时任务抢占。
| 组件 | 传统内核 | PREEMPT_RT 内核 |
|---|---|---|
| 软中断执行位置 | do_softirq()(软中断上下文) | ksoftirqd 内核线程(进程上下文) |
| 可睡眠 | ❌ | ✅ |
| 优先级 | 高于所有进程 | SCHED_FIFO 优先级 1(可被更高优先级任务抢占) |
| tasklet 实现 | 基于 TASKLET_SOFTIRQ 软中断 | 基于 tasklet_thread 内核线程 |
| 调度延迟 | 低(直接执行) | 略高(线程切换开销) |
4.3.3 tasklet 的线程化改造细节
tasklet 在传统内核中本质是软中断的一种特殊形式(TASKLET_SOFTIRQ)。PREEMPT_RT 将其改造为基于tasklet_thread 的内核线程执行模型:
- tasklet_schedule() 不再触发软中断,而是唤醒绑定的 tasklet 内核线程。
- tasklet 内核线程 以 SCHED_FIFO 优先级运行,可被实时任务抢占。
- tasklet 内部可以使用 mutex,因为现在运行在进程上下文中。
- 兼容性层:未修改的 tasklet 代码无需改动,底层机制自动切换。
4.3.4 线程化改造的收益与代价
| 评估维度 | 收益 | 代价 |
|---|---|---|
| 实时性 | 高优先级实时任务可抢占中断线程,保证 deadline | 中断响应延迟增加(线程调度 + 上下文切换) |
| 锁兼容性 | 中断线程可使用 mutex,简化驱动开发 | spin_lock 在 RT 内核中变为可睡眠锁(rt_mutex) |
| 代码复用 | 现有驱动代码无需修改 API | 需验证 tasklet 执行顺序依赖(线程化后可能乱序) |
| CPU 开销 | 减少关中断时间,提升系统整体响应 | 线程切换增加 CPU 负载(约 5%~15%) |
4.4 本章小结
- 中断上下文不可睡眠、不可长时间占用,是实时性的主要瓶颈。
- 中断线程化将中断处理迁移到进程上下文,由调度器统一管理优先级。
- 软中断被线程化为 ksoftirqd 内核线程(SCHED_FIFO 优先级 1)。
- tasklet 被线程化为 tasklet_thread,可在进程上下文中使用 mutex。
- 线程化改造以轻微增加中断延迟为代价,换取确定性的实时调度能力。