4. 中断机制详解:硬件中断、软中断(SoftIRQ)与 Tasklet 的执行流程

在网关驱动性能调优中,理解中断的完整生命周期是配置 CPU 亲和性与负载均衡的前提。中断处理从硬件触发到内核完成响应,经历了从硬件中断(Hard IRQ)到软中断(SoftIRQ),再到Tasklet 或工作队列(Workqueue) 的逐级分解过程。本章将深入剖析这三层机制的执行流程与协作关系。

4.1 硬件中断(Hard IRQ)

硬件中断由外设(如网卡、磁盘控制器)通过中断控制器(如 APIC、GIC)向 CPU 发送电信号触发。其核心目标是快速响应,在极短时间内完成必要操作后退出,避免阻塞其他中断。

关键特征:
  • 不可睡眠:中断上下文不能调用可能导致睡眠的函数(如 kmalloc(GFP_KERNEL))。
  • 不可重入:同一中断线在处理期间被屏蔽,防止嵌套。
  • 执行时间极短:通常只完成寄存器保存、中断号确认、清除中断标志等原子操作。

硬件中断处理程序(ISR)注册时使用 request_irq() 或 request_threaded_irq()。对于高性能网关驱动,通常将大部分工作推迟到下半部(Bottom Half)执行。

// 注册硬件中断处理程序示例
static irqreturn_t my_gateway_irq_handler(int irq, void *dev_id)
{
    struct net_device *dev = (struct net_device *)dev_id;
    // 1. 读取中断状态寄存器,确认中断源
    // 2. 清除硬件中断标志
    // 3. 触发软中断(调度 NAPI 或 Tasklet)
    napi_schedule(&napi_struct);
    return IRQ_HANDLED;
}

int request_irq(unsigned int irq, irq_handler_t handler,
                unsigned long flags, const char *name, void *dev);
调优要点:在网关场景中,网卡中断应尽量绑定到非繁忙的 CPU 核心,避免与系统管理中断(如时钟中断)争抢同一核心。使用 /proc/irq/{IRQ_NUM}/smp_affinity 设置亲和性。
硬件中断触发流程 外设(网卡) 中断控制器 CPU 核心 ISR 执行 说明: 1. 网卡收到数据包后,通过 PCIe 总线向中断控制器发送中断请求信号。 2. 中断控制器(如 x86 APIC)将中断向量路由到指定 CPU 核心。 3. CPU 保存当前上下文,跳转到注册的 ISR 函数执行。 4. ISR 完成最小必要操作后立即返回,触发软中断处理剩余工作。

4.2 软中断(SoftIRQ)

软中断是 Linux 内核中优先级最高的下半部机制,用于处理硬件中断遗留的、对时间要求仍然较高的任务。软中断在 do_softirq() 或 ksoftirqd 内核线程中执行,允许被硬件中断抢占,但自身不可睡眠。

特性 硬件中断(Hard IRQ) 软中断(SoftIRQ)
执行上下文 中断上下文 软中断上下文(可被硬中断抢占)
可睡眠 否 否
并发性 同一中断线串行 同一类型软中断可在多核并行
典型用途 清除中断标志、复制寄存器 网络协议栈处理(NET_RX_SOFTIRQ)
注册方式 request_irq() open_softirq()

内核预定义了若干软中断类型,其中与网关驱动最相关的是 NET_RX_SOFTIRQ 和 NET_TX_SOFTIRQ,分别处理网络数据包的接收和发送。

// 软中断注册与触发示例
enum
{
    HI_SOFTIRQ = 0,      // 高优先级 Tasklet
    TIMER_SOFTIRQ,       // 定时器
    NET_TX_SOFTIRQ,      // 网络发送
    NET_RX_SOFTIRQ,      // 网络接收
    BLOCK_SOFTIRQ,       // 块设备
    IRQ_POLL_SOFTIRQ,    // 中断轮询
    TASKLET_SOFTIRQ,     // 普通 Tasklet
    SCHED_SOFTIRQ,       // 调度器
    HRTIMER_SOFTIRQ,     // 高精度定时器
    RCU_SOFTIRQ          // RCU 回调
};

// 注册软中断处理函数
open_softirq(NET_RX_SOFTIRQ, net_rx_action);

// 在硬件中断中触发软中断
void __raise_softirq_irqoff(unsigned int nr);
性能陷阱:软中断在同一 CPU 上执行时,如果处理时间过长(例如超过 2 个 jiffies),内核会将其迁移到 ksoftirqd 内核线程中执行。频繁触发迁移会导致上下文切换开销增加,影响网关吞吐量。建议通过 /proc/softirqs 监控各 CPU 的软中断分布。
软中断调度与执行流程 硬件中断 ISR raise_softirq() do_softirq() net_rx_action ksoftirqd 线程 Tasklet 调度 执行路径说明: • 正常路径:ISR → raise_softirq → do_softirq → 处理函数(如 net_rx_action) • 超时路径:若 do_softirq 执行超过 2 个 jiffies,剩余工作交由 ksoftirqd 线程处理 • Tasklet 路径:软中断处理函数中可调度 Tasklet 进一步推迟非关键工作

4.3 Tasklet 执行流程

Tasklet 是建立在软中断之上的更灵活的下半部机制。与直接使用软中断不同,Tasklet 具有以下特性:

  • 同一 Tasklet 串行执行:同一类型的 Tasklet 在同一时刻只能在一个 CPU 上执行,无需额外加锁。
  • 不同 Tasklet 可并行:不同类型的 Tasklet 可以在不同 CPU 上同时执行。
  • 动态注册:可以在驱动初始化或运行时动态创建和调度 Tasklet。
// Tasklet 定义与使用示例
#include <linux/interrupt.h>

// 1. 声明 Tasklet 结构体
struct tasklet_struct my_tasklet;

// 2. Tasklet 处理函数
void my_tasklet_handler(unsigned long data)
{
    struct net_device *dev = (struct net_device *)data;
    // 处理数据包接收、统计更新等操作
    netif_rx(skb);  // 将数据包送入协议栈
}

// 3. 初始化 Tasklet
tasklet_init(&my_tasklet, my_tasklet_handler, (unsigned long)dev);

// 4. 在 ISR 或软中断中调度 Tasklet
tasklet_schedule(&my_tasklet);
对比维度 SoftIRQ Tasklet Workqueue
执行上下文 软中断上下文 软中断上下文 进程上下文
可睡眠 否 否 是
并发模型 同类型可多核并行 同类型串行 可多核并行
调度开销 低 低 中
典型场景 网络协议栈、块设备 驱动下半部、简单回调 复杂操作、文件系统
网关驱动选择建议:
  • 对于数据包接收这种高频、时间敏感的任务,优先使用 NAPI + SoftIRQ(NET_RX_SOFTIRQ)。
  • 对于链路状态变化通知、统计信息更新等低频任务,使用 Tasklet 即可。
  • 如果任务需要睡眠(如访问 I2C 设备、发送网络管理帧),必须使用 Workqueue。
Tasklet 生命周期 tasklet_init() tasklet_schedule() TASKLET_STATE_SCHED 处理函数执行 tasklet_kill() 可重新调度 注意:tasklet_kill() 会等待 Tasklet 执行完毕,在模块卸载时必须调用,防止野指针。

4.4 中断处理全流程总结

结合网关驱动场景,一个完整的中断处理流程如下:

  1. 硬件触发:网卡收到数据包,通过 MSI-X 或传统中断线向中断控制器发送中断。
  2. 中断路由:中断控制器根据亲和性配置将中断送达指定 CPU 核心。
  3. ISR 执行:CPU 保存上下文,执行注册的硬件中断处理程序,清除中断标志,触发 NAPI 或软中断。
  4. 软中断处理:do_softirq() 调用 net_rx_action(),从网卡接收队列中批量获取数据包。
  5. Tasklet/Workqueue:对于非紧急操作(如统计更新、链路状态通知),通过 Tasklet 或 Workqueue 进一步推迟执行。
  6. 协议栈处理:数据包通过 netif_receive_skb() 进入网络协议栈,最终送达用户空间。
性能调优核心原则:
  • 尽量减少硬件中断处理时间,将非必要工作全部推入下半部。
  • 利用 NAPI 的轮询机制减少高吞吐场景下的中断次数。
  • 通过 /proc/interrupts 和 /proc/softirqs 监控中断分布,确保多核负载均衡。
  • 对于多队列网卡,为每个队列分配独立的 IRQ 并绑定到不同 CPU 核心。
网关驱动完整中断处理流程 ① 网卡硬件中断 ② ISR 执行(最小操作) ③ NAPI 调度 / raise_softirq ④ net_rx_action() 轮询收包 ⑤ 协议栈处理 / Tasklet ⑥ 用户空间应用读取 各阶段耗时参考: ① 硬件延迟:~1-5 μs ② ISR 执行:~5-20 μs ③ 软中断调度:~1 μs ④ 轮询收包:~50-200 μs ⑤ 协议栈:~100-500 μs ⑥ 用户态:取决于应用 调优关注点: • ② 阶段应尽量短 • ④ 阶段利用 NAPI 批量 • ⑤ 阶段避免锁竞争 • 整体中断亲和性绑定