3. 软中断执行流程

软中断的执行并非简单地“调用一个函数”,而是由一套精密的调度与执行机制驱动。本章深入剖析软中断的核心执行路径: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() 的执行逻辑可以概括为以下几个步骤:

  1. 保存状态并禁止软中断重入:通过 __local_bh_disable_ip() 增加当前进程的软中断禁止计数,防止在处理过程中被其他软中断处理程序递归调用。
  2. 获取待处理软中断位图:读取当前 CPU 的 irq_cpustat_t.__softirq_pending 变量,获取所有待处理的软中断位图。
  3. 循环处理:遍历位图中的每一位,对于每个被置位的软中断号,调用其注册的处理函数 softirq_vec[nr].action()。
  4. 重新检查:在处理完一轮后,再次检查 __softirq_pending。如果仍有新的软中断被触发,则进行第二轮处理。为了防止用户态进程被饿死,内核设置了最大循环次数(MAX_SOFTIRQ_RESTART,通常为 10 次)。
  5. 唤醒 ksoftirqd:如果经过 MAX_SOFTIRQ_RESTART 次循环后,仍有软中断待处理,则不再继续在硬中断返回路径中执行,而是唤醒 ksoftirqd 内核线程来处理剩余的软中断。
  6. 恢复状态:最后,恢复软中断禁止计数,允许后续的软中断处理。
设计意图: 这种“尽力而为”的设计是为了平衡实时性与吞吐量。在硬中断返回路径中快速处理少量软中断,可以降低延迟;当负载过高时,将任务交给优先级较低的 ksoftirqd 线程,避免软中断长时间占用 CPU,从而保证用户态进程的调度。
do_softirq() 执行流程图 进入 do_softirq() 1. 禁止软中断重入 2. 获取 pending 位图 3. 遍历执行处理函数 还有待处理? 4. 重新检查 pending 超过 MAX 循环? 5. 唤醒 ksoftirqd 6. 恢复软中断状态 返回 是 否 是 否 ksoftirqd 接管后续处理

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 线程的主循环非常简单:

  1. 检查当前 CPU 的 __softirq_pending 位图。
  2. 如果非空,则调用 __do_softirq() 来处理软中断。
  3. 处理完毕后,线程主动让出 CPU(调用 schedule()),允许其他进程运行。
  4. 如果再次被唤醒,则重复上述步骤。
性能陷阱: 频繁唤醒 ksoftirqd 是系统软中断负载过高的信号。这通常意味着网络、块设备或定时器中断过于密集,导致软中断处理程序无法在硬中断返回路径中及时完成。此时,应关注是否发生了中断风暴或软中断处理程序本身存在性能瓶颈。
ksoftirqd 与 do_softirq 协作关系 硬件中断处理 irq_exit() do_softirq() 处理完? 返回 唤醒 ksoftirqd ksoftirqd 处理 是 否 说明 快速路径:在硬中断返回后立即执行,延迟低。 慢速路径:由 ksoftirqd 线程处理,优先级较低, 避免软中断长时间抢占 CPU。

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 的优先级相对较低,这符合其“可延迟执行”的设计初衷。
软中断优先级层次图 最高优先级:HI_SOFTIRQ (高优先级 Tasklet) 高优先级:TIMER_SOFTIRQ, NET_TX_SOFTIRQ, NET_RX_SOFTIRQ 中优先级:BLOCK_SOFTIRQ, IRQ_POLL_SOFTIRQ 低优先级:TASKLET_SOFTIRQ 最低优先级:SCHED_SOFTIRQ, HRTIMER_SOFTIRQ, RCU_SOFTIRQ 说明 软中断的优先级由软中断号决定,数值越小优先级越高。同一 CPU 上,高优先级的软中断会先于低优先级的软中断执行。

3.4 总结与关键点

  • do_softirq() 是软中断的执行引擎,它通过循环处理待处理软中断,并在负载过高时唤醒 ksoftirqd 线程。
  • ksoftirqd 是一个 per-CPU 的实时内核线程,作为软中断的“慢速路径”,防止软中断长时间抢占 CPU。
  • 软中断的优先级 由软中断号决定,数值越小优先级越高。网络和定时器相关的软中断具有较高的优先级,而普通 Tasklet 的优先级相对较低。
  • 理解软中断的执行流程和优先级,对于分析和优化内核中延迟敏感的任务(如网络、存储)至关重要。