4. 中断线程化原理:中断上半部与下半部

中断线程化是 PREEMPT_RT 补丁对 Linux 中断处理机制最核心的改造之一。其目标是将原本在硬中断上下文(不可抢占、不可睡眠)中执行的中断处理程序,迁移到可睡眠、可被抢占的内核线程上下文中执行,从而大幅降低系统的最坏情况延迟(Worst-Case Latency),并消除优先级反转等实时性问题。

4.1 传统中断处理:上半部与下半部

在标准 Linux 内核中,中断处理被明确划分为两个阶段:

  • 上半部(Top Half):在硬中断上下文中立即执行,必须快速、简短、不可睡眠。通常只完成最紧急的硬件操作(如清除中断标志、拷贝 FIFO 数据),然后触发下半部。
  • 下半部(Bottom Half):在稍后的时间点执行,允许被其他中断打断,但仍然运行在软中断(SoftIRQ)或 tasklet 上下文中,不可睡眠。常见的下半部机制包括 SoftIRQ、Tasklet 和 Workqueue。
关键问题: 传统下半部(SoftIRQ/Tasklet)虽然比上半部灵活,但仍然运行在中断上下文中,无法被高优先级实时任务抢占,导致实时任务必须等待所有挂起的软中断处理完毕才能获得 CPU,这引入了不可预测的延迟抖动(Jitter)。

下表总结了传统中断处理各阶段的特性对比:

特性 上半部 (Top Half) 下半部 (Bottom Half) 说明
执行上下文 硬中断上下文 软中断上下文 均不可睡眠
可抢占性 不可抢占 不可被进程抢占 可被更高优先级中断打断
允许操作 极简硬件操作 数据处理、唤醒任务 禁止获取信号量、禁止睡眠
延迟影响 直接增加中断延迟 阻塞实时任务调度 主要抖动来源
实时性痛点: 在标准内核中,一个高优先级实时任务可能因为一个正在执行的 SoftIRQ 而延迟数百微秒甚至毫秒,这在工业控制或音频处理场景中是不可接受的。

4.2 中断线程化机制

PREEMPT_RT 补丁引入了中断线程化(Interrupt Threading)机制,将中断处理程序的大部分工作(即原来的下半部逻辑)移到一个内核线程中执行。这个线程具有以下特点:

  • 可睡眠:线程化后的中断处理程序可以调用 wait_event()、mutex_lock() 等可能导致睡眠的函数。
  • 可被抢占:该线程拥有自己的优先级(通常继承自中断号或用户配置),可以被更高优先级的实时任务或中断线程抢占。
  • 独立调度实体:作为 SCHED_FIFO 或 SCHED_RR 实时线程参与内核调度。

中断线程化的核心 API 是 request_threaded_irq(),其函数原型如下:

int request_threaded_irq(unsigned int irq,
                         irq_handler_t handler,
                         irq_handler_t thread_fn,
                         unsigned long irqflags,
                         const char *devname,
                         void *dev_id);

参数说明:

  • irq:中断号。
  • handler:上半部函数(硬中断上下文)。如果为 NULL,则内核会自动调用 irq_default_primary_handler(),该函数立即唤醒线程。
  • thread_fn:线程化处理函数(线程上下文)。这是实际执行大部分工作的函数。
  • irqflags:中断标志,如 IRQF_TRIGGER_RISING、IRQF_SHARED 等。
  • devname:设备名称。
  • dev_id:设备 ID,用于共享中断。
重要规则: 如果 handler 返回 IRQ_WAKE_THREAD,则内核会唤醒对应的中断线程来执行 thread_fn。如果 handler 返回 IRQ_HANDLED,则不会唤醒线程。这允许驱动在极少数情况下(如快速确认硬件状态)选择不线程化处理。

下图展示了中断线程化的完整处理流程:

中断线程化处理流程 硬件中断触发 上半部 (handler) 硬中断上下文 · 快速硬件操作 返回 IRQ_WAKE_THREAD? 是 否 中断线程 (thread_fn) 线程上下文 · 可睡眠 · 可抢占 处理完成 不唤醒线程 实时调度器参与 SCHED_FIFO 优先级管理 💡 核心优势: 高优先级实时任务可抢占中断线程

4.3 强制线程化与自愿线程化策略

PREEMPT_RT 提供了两种中断线程化策略,以适应不同的硬件和驱动场景:

4.3.1 强制线程化 (Forced Threading)

当内核配置了 CONFIG_IRQ_FORCED_THREADING 选项(在 PREEMPT_RT 内核中默认启用),所有中断处理程序必须被线程化。即使驱动调用的是传统的 request_irq(),内核也会自动将其转换为线程化形式。

  • 触发条件:内核启动参数 threadirqs 或在 PREEMPT_RT 内核中默认生效。
  • 行为:内核为每个中断创建一个内核线程,名为 irq/<irq_number>-<device_name>。
  • 优点:统一所有中断处理行为,彻底消除软中断导致的不可抢占问题。
  • 缺点:对于极低延迟要求的硬件(如某些高速网络设备),线程切换开销可能影响吞吐量。

4.3.2 自愿线程化 (Voluntary Threading)

驱动开发者通过显式调用 request_threaded_irq() 并合理设计上半部与线程函数,自主决定哪些中断需要线程化。这是更精细的控制方式。

  • 触发条件:驱动代码显式使用 request_threaded_irq()。
  • 行为:只有调用该 API 的中断会被线程化,其他使用 request_irq() 的中断保持传统方式。
  • 优点:驱动开发者可以针对硬件特性优化,例如将时间关键型操作留在上半部。
  • 缺点:需要驱动开发者对实时性有深入理解,否则可能引入新的问题。
实践建议: 在 PREEMPT_RT 系统中,推荐使用强制线程化作为基础策略,然后针对性能关键型设备(如 GPU、高速网卡)通过驱动优化实现自愿线程化的混合模式。

下表对比了两种策略的关键差异:

对比维度 强制线程化 自愿线程化
控制粒度 系统全局 每个中断独立
配置方式 内核配置 + 启动参数 驱动代码 API 调用
上半部执行 仅确认中断、唤醒线程 可包含自定义快速处理
适用场景 通用实时系统、工业控制 高性能网络、音视频处理
延迟确定性 高(统一调度) 中(依赖驱动实现)

4.4 中断线程的优先级与调度

每个中断线程都是一个独立的实时内核线程,其优先级管理遵循以下规则:

  • 默认优先级:中断线程的优先级由中断号决定,通常 prio = 50 - irq_number(数值越小优先级越高)。这确保了低中断号(高优先级中断)的线程获得更高的调度优先级。
  • 用户自定义:可以通过 chrt 命令或 sched_setscheduler() 系统调用动态调整中断线程的优先级和调度策略。
  • 优先级继承:当中断线程持有互斥锁(mutex)并被高优先级任务阻塞时,PREEMPT_RT 的优先级继承协议(Priority Inheritance Protocol)会临时提升中断线程的优先级,防止优先级反转。
调试技巧: 使用 ps -eo pid,pri,rtprio,cmd | grep irq/ 可以查看系统中所有中断线程的实时优先级。例如:irq/50-i2c 的默认优先级为 0(最高),而 irq/130-eth0 的优先级为 -80(较低)。

4.5 中断线程化的实际影响

中断线程化对系统行为产生了深远影响,主要体现在以下几个方面:

  1. 上下文切换开销:每次中断处理至少需要两次上下文切换(从进程上下文到内核线程,再从内核线程返回),增加了中断延迟的绝对值。
  2. 缓存污染减少:由于中断线程可以被抢占,高优先级实时任务可以更快获得 CPU,减少了因中断处理导致的缓存污染。
  3. 锁机制变化:中断处理程序不再运行在原子上下文中,因此可以使用 mutex_lock() 替代 spin_lock_irqsave(),简化了驱动开发。
  4. 中断嵌套:线程化的中断处理程序可以被更高优先级的中断线程抢占,实现了中断的嵌套处理,进一步提高了实时性。
注意事项: 中断线程化并非万能。对于某些需要极低延迟(微秒级)的硬件,如 FPGA 或高速数据采集卡,线程切换的开销可能仍然过大。此时需要结合硬件中断控制器(如 GIC)的优先级管理功能,将关键中断配置为快速中断(FIQ)或使用 NAPI 轮询机制。

下图展示了中断线程化前后系统调度行为的对比:

中断线程化前后调度对比 传统模式 (无线程化) 实时任务 A (优先级 80) 中断处理 SoftIRQ 处理 (不可抢占) 实时任务 A 被阻塞等待 PREEMPT_RT 模式 (线程化) 实时任务 A (优先级 80) 上半部 中断线程 (优先级 50) 实时任务 A 抢占中断线程继续执行 🔍 关键差异分析: 1. 传统模式:SoftIRQ 处理期间,实时任务 A 被完全阻塞,即使其优先级更高。 2. PREEMPT_RT 模式:上半部快速完成后,中断线程以较低优先级运行。 3. 实时任务 A 可以抢占中断线程,在微秒级内恢复执行。 4. 中断线程在实时任务 A 睡眠或完成后继续执行剩余处理。