3. 软中断执行流程:do_softirq() 函数分析、ksoftirqd 内核线程、软中断的优先级与调度
软中断的执行并非简单地“调用一个函数”,而是由一套精密的调度与执行机制驱动。本章深入剖析软中断的核心执行路径:do_softirq() 函数、用于负载均衡的 ksoftirqd 内核线程,以及软中断的优先级与调度策略。
3.1 do_softirq() 函数:软中断的执行入口
do_softirq() 是软中断处理的核心函数,它负责在合适的时机遍历并执行所有待处理的软中断。该函数通常在以下两种上下文中被调用:
- 硬件中断返回路径:在
irq_exit()中,当检测到有软中断挂起且当前不在硬中断上下文时,调用invoke_softirq(),最终进入do_softirq()。 - ksoftirqd 内核线程:当软中断负载过重时,由
wakeup_softirqd()唤醒该线程,在线程上下文中执行do_softirq()。
关键点:
do_softirq() 的执行具有严格的限制:它必须在开中断(local_irq_enable())的环境下运行,并且不允许嵌套执行。同一 CPU 上,软中断的处理是串行化的。
3.1.1 执行流程分解
do_softirq() 的执行逻辑可以概括为以下几个步骤:
- 保存状态并禁止软中断重入:通过
__local_bh_disable_ip()增加当前进程的软中断禁止计数,防止在处理过程中被其他软中断处理程序递归调用。 - 获取待处理软中断位图:读取当前 CPU 的
irq_cpustat_t.__softirq_pending变量,获取所有待处理的软中断位图。 - 循环处理:遍历位图中的每一位,对于每个被置位的软中断号,调用其注册的处理函数
softirq_vec[nr].action()。 - 重新检查:在处理完一轮后,再次检查
__softirq_pending。如果仍有新的软中断被触发,则进行第二轮处理。为了防止用户态进程被饿死,内核设置了最大循环次数(MAX_SOFTIRQ_RESTART,通常为 10 次)。 - 唤醒 ksoftirqd:如果经过
MAX_SOFTIRQ_RESTART次循环后,仍有软中断待处理,则不再继续在硬中断返回路径中执行,而是唤醒ksoftirqd内核线程来处理剩余的软中断。 - 恢复状态:最后,恢复软中断禁止计数,允许后续的软中断处理。
设计意图: 这种“尽力而为”的设计是为了平衡实时性与吞吐量。在硬中断返回路径中快速处理少量软中断,可以降低延迟;当负载过高时,将任务交给优先级较低的
ksoftirqd 线程,避免软中断长时间占用 CPU,从而保证用户态进程的调度。
3.2 ksoftirqd 内核线程:软中断的后备处理者
ksoftirqd 是一个 per-CPU 的内核线程,其名称格式为 ksoftirqd/%u(其中 %u 是 CPU 编号)。它的主要职责是处理那些在硬中断返回路径中未能处理完的“残留”软中断。
3.2.1 线程的创建与唤醒
- 创建:在系统启动期间,由
spawn_ksoftirqd()函数为每个在线 CPU 创建一个ksoftirqd线程。该线程被设置为SCHED_FIFO实时调度策略,优先级为49(即MAX_RT_PRIO - 1),确保其优先级高于所有普通进程,但低于硬中断处理。 - 唤醒:当
do_softirq()在硬中断返回路径中执行了MAX_SOFTIRQ_RESTART次循环后,仍有待处理的软中断时,会调用wakeup_softirqd()唤醒当前 CPU 的ksoftirqd线程。
3.2.2 线程的执行逻辑
ksoftirqd 线程的主循环非常简单:
- 检查当前 CPU 的
__softirq_pending位图。 - 如果非空,则调用
__do_softirq()来处理软中断。 - 处理完毕后,线程主动让出 CPU(调用
schedule()),允许其他进程运行。 - 如果再次被唤醒,则重复上述步骤。
性能陷阱: 频繁唤醒
ksoftirqd 是系统软中断负载过高的信号。这通常意味着网络、块设备或定时器中断过于密集,导致软中断处理程序无法在硬中断返回路径中及时完成。此时,应关注是否发生了中断风暴或软中断处理程序本身存在性能瓶颈。
3.3 软中断的优先级与调度
软中断的优先级体现在两个层面:执行时机 和 处理顺序。
3.3.1 执行时机优先级
从 CPU 执行的角度看,优先级从高到低排列如下:
| 优先级 | 执行上下文 | 说明 |
|---|---|---|
| 最高 | 硬件中断处理程序 | 立即执行,不可被其他中断(同级别或更低)抢占,但可被更高级中断抢占。 |
| 高 | 软中断 (在 irq_exit 中) |
在硬件中断返回后立即执行,此时硬中断已开启,但软中断禁止重入。 |
| 中 | ksoftirqd 内核线程 | 作为实时线程运行,优先级为 49,高于所有普通进程(优先级 50-99)。 |
| 低 | 普通用户态进程 | 优先级最低,只有在没有软中断或硬中断需要处理时才能获得 CPU。 |
3.3.2 处理顺序优先级
在同一 CPU 上,当 do_softirq() 遍历 __softirq_pending 位图时,它按照软中断号从小到大的顺序依次执行。软中断号定义在 <linux/interrupt.h> 中,其数值决定了优先级:数值越小,优先级越高。
以下是内核中定义的几个关键软中断及其优先级:
| 软中断号 | 名称 | 关联子系统 | 优先级说明 |
|---|---|---|---|
| 0 | HI_SOFTIRQ |
高优先级 Tasklet | 最高优先级软中断,用于需要快速响应的 Tasklet。 |
| 1 | TIMER_SOFTIRQ |
定时器 | 处理所有常规定时器(timer_list)的到期回调。 |
| 2 | NET_TX_SOFTIRQ |
网络发送 | 处理网络数据包的发送完成回调。 |
| 3 | NET_RX_SOFTIRQ |
网络接收 | 处理网络数据包的接收回调,是网络吞吐量的关键路径。 |
| 4 | BLOCK_SOFTIRQ |
块设备 | 处理块设备 I/O 完成回调。 |
| 5 | IRQ_POLL_SOFTIRQ |
中断轮询 | 用于支持中断轮询模式的设备。 |
| 6 | TASKLET_SOFTIRQ |
普通 Tasklet | 处理所有普通优先级的 Tasklet。 |
| 7 | SCHED_SOFTIRQ |
调度器 | 用于负载均衡等调度器维护工作。 |
| 8 | HRTIMER_SOFTIRQ |
高精度定时器 | 处理高精度定时器的到期回调。 |
| 9 | RCU_SOFTIRQ |
RCU 机制 | 处理 RCU 回调,用于实现无锁同步。 |
设计考量: 网络相关的软中断(
NET_TX_SOFTIRQ 和 NET_RX_SOFTIRQ)被赋予较高的优先级,这反映了网络性能对系统响应速度的敏感性。定时器(TIMER_SOFTIRQ)的优先级也较高,以确保时间敏感任务得到及时处理。而普通 Tasklet 的优先级相对较低,这符合其“可延迟执行”的设计初衷。
3.4 总结与关键点
- do_softirq() 是软中断的执行引擎,它通过循环处理待处理软中断,并在负载过高时唤醒
ksoftirqd线程。 - ksoftirqd 是一个 per-CPU 的实时内核线程,作为软中断的“慢速路径”,防止软中断长时间抢占 CPU。
- 软中断的优先级 由软中断号决定,数值越小优先级越高。网络和定时器相关的软中断具有较高的优先级,而普通 Tasklet 的优先级相对较低。
- 理解软中断的执行流程和优先级,对于分析和优化内核中延迟敏感的任务(如网络、存储)至关重要。