2. 软中断机制
软中断(SoftIRQ)是 Linux 内核中断下半部最基础的机制,也是 Tasklet 和 Workqueue 的底层支撑。理解软中断的定义、类型划分以及注册/触发流程,是掌握整个下半部系统的关键。
核心要点:软中断是一种在硬件中断返回后、进程上下文恢复前执行的“软件中断请求”。它运行在
hardirq 上下文中,但允许开中断,具有严格优先级和固定类型。
2.1 软中断的定义与设计目标
软中断是内核预定义的一组静态中断处理函数,用于处理对时间要求严格、但不需要在硬件中断服务程序(ISR)中完成的任务。其设计目标包括:
- 低延迟:在硬件中断返回后立即执行,减少中断屏蔽时间。
- 可重入性:同一类型的软中断可以在不同 CPU 上并行执行(但同一 CPU 上串行)。
- 优先级区分:通过软中断号(softirq number)决定执行顺序,高优先级软中断优先处理。
- 轻量级:不涉及进程调度,直接在内核线程或中断返回路径中执行。
注意:软中断是“静态”的——类型在编译时确定,不能动态创建。如果需要动态创建下半部任务,应使用 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 来扩展下半部功能。
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 触发流程
- 置位软中断状态位:将
__softirq_pending中对应位设为 1。 - 检查执行上下文:
- 如果在硬件中断上下文中,标记
ksoftirqd唤醒请求。 - 如果在软中断上下文中,允许嵌套触发。
- 如果在进程上下文中,直接调用
do_softirq()尝试执行。
- 如果在硬件中断上下文中,标记
- 执行时机:
- 硬件 ISR 返回时(
irq_exit()中调用invoke_softirq())。 - 内核线程
ksoftirqd被唤醒后执行。 - 显式调用
do_softirq()或local_bh_enable()时。
- 硬件 ISR 返回时(
2.5 软中断的执行上下文约束
重要约束:软中断处理函数运行在中断上下文中,必须遵守以下规则:
- 不能调用可能导致睡眠的函数(如
kmalloc(GFP_KERNEL)、mutex_lock())。 - 不能直接触发进程调度(不能调用
schedule())。 - 不能访问用户空间内存(因为不关联任何进程)。
- 处理时间应尽量短,避免长时间占用 CPU。
2.6 软中断与 Tasklet 的关系
Tasklet 是基于软中断实现的高层机制,理解两者的关系有助于在实际开发中做出正确选择:
| 特性 | 软中断 (SoftIRQ) | Tasklet |
|---|---|---|
| 类型数量 | 固定(最多 10 个) | 动态创建(无数量限制) |
| 注册方式 | open_softirq() 编译时注册 |
tasklet_init() 运行时注册 |
| 并行性 | 同一类型可在多 CPU 并行 | 同一 Tasklet 串行执行 |
| 使用复杂度 | 高(需考虑重入和同步) | 低(自动串行化) |
| 典型用途 | 网络、块设备、定时器等核心子系统 | 驱动中的下半部任务 |
实践建议:绝大多数驱动开发者不需要直接使用软中断。当需要高性能、可并行的下半部处理时(如网卡驱动),才考虑直接操作软中断;否则优先使用 Tasklet 或 Workqueue。