2. 软中断机制:软中断的定义、类型、注册与触发

软中断(SoftIRQ)是 Linux 内核中断下半部最基础的机制,也是 Tasklet 和 Workqueue 的底层支撑。理解软中断的定义、类型划分以及注册/触发流程,是掌握整个下半部系统的关键。

核心要点:软中断是一种在硬件中断返回后、进程上下文恢复前执行的“软件中断请求”。它运行在 hardirq 上下文中,但允许开中断,具有严格优先级和固定类型。

2.1 软中断的定义与设计目标

软中断是内核预定义的一组静态中断处理函数,用于处理对时间要求严格、但不需要在硬件中断服务程序(ISR)中完成的任务。其设计目标包括:

  • 低延迟:在硬件中断返回后立即执行,减少中断屏蔽时间。
  • 可重入性:同一类型的软中断可以在不同 CPU 上并行执行(但同一 CPU 上串行)。
  • 优先级区分:通过软中断号(softirq number)决定执行顺序,高优先级软中断优先处理。
  • 轻量级:不涉及进程调度,直接在内核线程或中断返回路径中执行。
注意:软中断是“静态”的——类型在编译时确定,不能动态创建。如果需要动态创建下半部任务,应使用 Tasklet 或 Workqueue。
软中断在中断处理流程中的位置 硬件中断 (ISR) 软中断 (SoftIRQ) Tasklet Workqueue 说明: 软中断在硬件 ISR 返回后立即执行,Tasklet 基于软中断实现,Workqueue 运行在进程上下文。

2.2 软中断的类型

Linux 内核预定义了若干软中断类型,每种类型对应一个优先级编号(编号越小优先级越高)。下表列出了内核中主要的软中断类型及其用途:

软中断编号 宏定义名称 优先级 用途说明
0 HI_SOFTIRQ 最高 高优先级 Tasklet(TASKLET_SOFTIRQ 之前执行)
1 TIMER_SOFTIRQ 高 定时器(timer)到期处理、动态时钟 tick
2 NET_TX_SOFTIRQ 中高 网络数据包发送完成后的清理与重传
3 NET_RX_SOFTIRQ 中高 网络数据包接收处理(协议栈入口)
4 BLOCK_SOFTIRQ 中 块设备 I/O 完成回调(如磁盘读写完成)
5 IRQ_POLL_SOFTIRQ 中 中断轮询模式(用于高吞吐设备)
6 TASKLET_SOFTIRQ 低 普通 Tasklet 执行
7 SCHED_SOFTIRQ 低 调度器负载均衡(CFS 调度)
8 HRTIMER_SOFTIRQ 低 高精度定时器(hrtimer)回调
9 RCU_SOFTIRQ 最低 RCU(Read-Copy-Update)宽限期处理
重要:软中断类型数量受 NR_SOFTIRQS 限制(通常为 10)。新增软中断类型需要修改内核源码并重新编译,因此内核社区推荐使用 Tasklet 或 Workqueue 来扩展下半部功能。
软中断类型优先级层次 高优先级 低优先级 HI_SOFTIRQ (0) TIMER_SOFTIRQ (1) NET_TX/RX_SOFTIRQ (2,3) BLOCK_SOFTIRQ (4) TASKLET_SOFTIRQ (6) RCU_SOFTIRQ (9) 优先级执行规则: 1. 软中断按编号升序执行(0→9) 2. 高优先级软中断可以抢占低优先级 3. 同一 CPU 上串行执行,不同 CPU 可并行 4. 软中断执行期间允许硬件中断嵌套 5. 软中断不能睡眠、不能阻塞 典型执行顺序示例: 硬件 ISR → HI_SOFTIRQ → TIMER_SOFTIRQ → NET_RX_SOFTIRQ → TASKLET_SOFTIRQ → RCU_SOFTIRQ → 返回进程上下文

2.3 软中断的注册

软中断的注册通过 open_softirq() 函数完成,该函数将软中断编号与处理函数绑定。注册过程在内核初始化阶段完成,驱动模块通常不直接注册软中断。

2.3.1 注册函数原型

void open_softirq(int nr, void (*action)(struct softirq_action *));
  • nr:软中断编号(0 ~ NR_SOFTIRQS-1)
  • action:处理函数指针,参数为 struct softirq_action *

2.3.2 内核初始化注册示例

以下代码展示了内核在启动过程中如何注册网络接收软中断:

// 文件: kernel/softirq.c (内核初始化路径)
void __init softirq_init(void)
{
    int cpu;

    for_each_possible_cpu(cpu) {
        per_cpu(tasklet_vec, cpu) = NULL;
        per_cpu(tasklet_hi_vec, cpu) = NULL;
    }

    open_softirq(TASKLET_SOFTIRQ, tasklet_action);
    open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}

// 文件: net/core/dev.c (网络子系统初始化)
static int __init net_dev_init(void)
{
    // ...
    open_softirq(NET_TX_SOFTIRQ, net_tx_action);
    open_softirq(NET_RX_SOFTIRQ, net_rx_action);
    // ...
}
关键数据结构:每个软中断对应一个 struct softirq_action 结构体,内核维护一个全局数组 softirq_vec[NR_SOFTIRQS] 来存储所有注册的处理函数。
// 定义在 include/linux/interrupt.h
struct softirq_action {
    void (*action)(struct softirq_action *);
};

// 全局数组
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;

2.4 软中断的触发

软中断的触发通过 raise_softirq() 或 raise_softirq_irqoff() 函数实现。触发操作将当前 CPU 的软中断状态位图中对应位置位,并在适当的时候执行处理函数。

2.4.1 触发函数

函数 是否关中断 使用场景
raise_softirq(unsigned int nr) 自动关中断 进程上下文或已知中断已开启时
raise_softirq_irqoff(unsigned int nr) 调用者已关中断 硬件 ISR 或已关中断的临界区

2.4.2 触发流程

  1. 置位软中断状态位:将 __softirq_pending 中对应位设为 1。
  2. 检查执行上下文:
    • 如果在硬件中断上下文中,标记 ksoftirqd 唤醒请求。
    • 如果在软中断上下文中,允许嵌套触发。
    • 如果在进程上下文中,直接调用 do_softirq() 尝试执行。
  3. 执行时机:
    • 硬件 ISR 返回时(irq_exit() 中调用 invoke_softirq())。
    • 内核线程 ksoftirqd 被唤醒后执行。
    • 显式调用 do_softirq() 或 local_bh_enable() 时。
软中断触发与执行流程 硬件 ISR 调用 raise_softirq() 进程上下文调用 raise_softirq() ksoftirqd 内核线程 置位 __softirq_pending (设置对应软中断位) 检查执行上下文 (hardirq/softirq/process) 调用 do_softirq() 遍历 softirq_vec[] 按优先级执行处理函数 执行时机总结: ① irq_exit() 中检查并执行 ② ksoftirqd 线程被唤醒 ③ 显式调用 local_bh_enable() 或 do_softirq()

2.5 软中断的执行上下文约束

重要约束:软中断处理函数运行在中断上下文中,必须遵守以下规则:
  • 不能调用可能导致睡眠的函数(如 kmalloc(GFP_KERNEL)、mutex_lock())。
  • 不能直接触发进程调度(不能调用 schedule())。
  • 不能访问用户空间内存(因为不关联任何进程)。
  • 处理时间应尽量短,避免长时间占用 CPU。

2.6 软中断与 Tasklet 的关系

Tasklet 是基于软中断实现的高层机制,理解两者的关系有助于在实际开发中做出正确选择:

特性 软中断 (SoftIRQ) Tasklet
类型数量 固定(最多 10 个) 动态创建(无数量限制)
注册方式 open_softirq() 编译时注册 tasklet_init() 运行时注册
并行性 同一类型可在多 CPU 并行 同一 Tasklet 串行执行
使用复杂度 高(需考虑重入和同步) 低(自动串行化)
典型用途 网络、块设备、定时器等核心子系统 驱动中的下半部任务
实践建议:绝大多数驱动开发者不需要直接使用软中断。当需要高性能、可并行的下半部处理时(如网卡驱动),才考虑直接操作软中断;否则优先使用 Tasklet 或 Workqueue。