Linux 软中断与 ksoftirqd:网络高负载下的 CPU 飙升排查

深夜收到告警:某台 Nginx 网关机 CPU 使用率突破 90%,但 top 里看不到任何用户态进程占用大量 CPU。%sy 高达 60% 以上,%si 也异常突出。这不是应用代码的锅,而是 Linux 内核网络栈在向你求救。本文从一次真实排查出发,讲清软中断、ksoftirqd 与网络高负载之间的关系,并给出可落地的定位与优化手段。

一、先分清:硬中断、软中断与 ksoftirqd

网卡收到数据包时,首先触发的是硬中断。硬中断要求极快地完成,因此内核只做最紧急的事:记录中断、屏蔽网卡中断、唤醒软中断处理。真正耗时的协议栈处理(从链路层到传输层、socket 排队等)被推迟到软中断中执行。

软中断(softirq)是一组静态注册的延迟执行机制,常见类型包括:

  • NET_RX_SOFTIRQ:网络接收
  • NET_TX_SOFTIRQ:网络发送
  • TIMER_SOFTIRQ:定时器
  • TASKLET_SOFTIRQ:tasklet
  • BLOCK_SOFTIRQ:块设备

软中断有两个执行时机:一是硬中断返回前(irq_exit)顺带执行,二是由内核线程 ksoftirqd 在后台执行。每个 CPU 都有一个 ksoftirqd 线程,命名形如 ksoftirqd/0ksoftirqd/1

为什么要引入 ksoftirqd?因为软中断如果一直“处理完再返回”,在高流量下会导致用户态进程长期得不到调度,甚至看门狗报 soft lockup。于是内核设定:当软中断负载过重或执行时间过长时,把剩余工作交给 ksoftirqd,让它以普通线程身份参与调度,保证公平性。

这也就解释了一个现象:网络高负载时,CPU 时间会记在 %si(软中断)和 ksoftirqd 线程上,而不是你的业务进程上。

二、现场定位:从 top 到 /proc/softirqs

1. 看整体分布

top -H

重点关注:

  • %si:软中断占用。持续偏高说明网络/定时器软中断压力大。
  • %sy:内核态占用。协议栈处理、系统调用都算在这里。
  • ksoftirqd 线程是否出现在 CPU 占用前列。

如果 %si 高且 ksoftirqd 活跃,基本可以锁定是软中断处理不过来。

2. 看软中断类型分布

cat /proc/softirqs

这个文件按 CPU 列出各类软中断的累计次数。排查要点:

  • 多次采样,观察 NET_RX 的增量是否远大于其他类型。
  • 对比各 CPU 的 NET_RX 增量是否严重不均。

如果只有 CPU0 的 NET_RX 疯涨,说明中断亲和性配置有问题,所有网卡中断都压在了一个核上。

3. 看网卡中断分布

cat /proc/interrupts | grep -i eth

查看每个中断号在各 CPU 上的计数。若某块网卡的中断全部落在 CPU0,就是典型的单核瓶颈。

4. 看软中断的实时耗时

watch -n1 'cat /proc/softirqs'

或使用 mpstat -P ALL 1 观察每个 CPU 的 %soft 列,定位是哪颗核在扛软中断。

5. 用 perf 找到热点函数

perf top -g

在网络高负载场景下,常见热点会落在 __netif_receive_skb_coreip_rcvtcp_v4_rcvnet_rx_action 等函数上。这能帮你确认瓶颈确实在协议栈收包路径。

三、常见根因与对应优化

根因一:中断亲和性不合理

所有网卡中断集中在 CPU0,导致单核软中断饱和。

优化:启用 irqbalance 服务,或手动设置中断亲和性:

# 查看网卡队列对应的中断号
grep eth0 /proc/interrupts

# 将中断 128 绑定到 CPU2(掩码 0x4)
echo 4 > /proc/irq/128/smp_affinity

多队列网卡建议配合 RSS(接收端缩放),让不同队列的中断分散到不同 CPU。

根因二:单队列网卡或 RSS 未生效

如果网卡只有一个 RX 队列,无论怎么绑核都只有一个 CPU 处理收包。

优化:确认网卡是否支持多队列:

ethtool -l eth0

若支持,调整队列数:

ethtool -L eth0 combined 8

根因三:小包太多,软中断处理开销大

大量小包会让每个包都走一遍完整的软中断路径,单位字节的处理成本极高。

优化:

  • 开启 GRO/GSO 合并,减少上层看到的包数量:
ethtool -K eth0 gro on gso on
  • 对于 UDP 场景,考虑使用 SO_REUSEPORT 让多个 socket 分担收包,或使用 XDP/AF_XDP 在更早阶段处理。
  • 开启 RPS(接收包转向),把软中断处理分散到多个 CPU:
echo f > /sys/class/net/eth0/queues/rx-0/rps_cpus

RPS 适用于单队列网卡,用软件方式模拟多队列。

根因四:ksoftirqd 被饿死或调度不及时

ksoftirqd 是普通线程,如果系统里有更高优先级的实时任务长期占用 CPU,ksoftirqd 得不到调度,软中断积压。

优化:检查是否有 RT 任务霸占 CPU,必要时调整 ksoftirqd 的调度策略或优先级,但需谨慎评估。

根因五:协议栈本身成为瓶颈

在极高 PPS 场景下,即使中断分散、GRO 开启,内核协议栈仍然可能到顶。

优化方向:

  • 使用 SO_REUSEPORT 多进程分摊 accept 压力。
  • 考虑 eBPF/XDP 在驱动层做过滤或转发,绕过部分协议栈。
  • 评估 DPDK 等用户态网络方案。

四、一个可复用的排查清单

  1. top -H 确认 %si 高、ksoftirqd 活跃。
  2. cat /proc/softirqs 确认 NET_RX 增量异常。
  3. cat /proc/interrupts 确认中断是否集中。
  4. mpstat -P ALL 1 确认是否单核 %soft 饱和。
  5. perf top 确认热点在收包路径。
  6. 按“中断分散 → 多队列 → GRO/RPS → 协议栈旁路”的顺序逐层优化。

五、小结

Linux 软中断与 ksoftirqd 是网络高负载下 CPU 飙升的常见“隐形推手”。它们把耗时的协议栈处理从硬中断中剥离出来,保证了系统响应性,但在流量洪峰下也会成为瓶颈。排查的核心思路是:先确认软中断类型和 CPU 分布,再定位是中断亲和性、队列数、包大小还是协议栈本身的问题,最后按层次选择绑核、多队列、GRO/RPS 或旁路方案。

理解这套机制,下次再看到 %si 飙升,你就不会只盯着业务进程发呆了。

未经允许不得转载:任鹏个人博客 » Linux 软中断与 ksoftirqd:网络高负载下的 CPU 飙升排查

赞 (0) 打赏

评论 0

取消
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏