服务器CPU软中断过高是什么原因?网络收发与内核处理分析

一台服务器 CPU 看着没用满,但 top 里 si 那一列——也就是软中断——却常年飘红,甚至单核被打到 100%。这种故障特别隐蔽:业务进程本身没吃多少 CPU,可网络就是慢、丢包、SSH 发卡,用 top 看又找不到明显的大户。问题不在应用代码,而在内核处理网络数据包的路径上。

这篇文章聚焦软中断(softirq),也就是内核把网卡收包、发包、定时器这些"急着但可以稍后做"的工作,从硬中断里剥离出来执行的机制。我会讲清楚软中断为什么会产生、ksoftirqd 是什么、什么情况下会失控,以及用 top、mpstat、/proc/softirqs、sar 这些工具怎么把它量化出来并定位到具体网卡。

很多人一看到软中断高就去调内核参数,结果越调越糟。其实软中断高大多有明确的外部原因——流量突增、DDoS、网卡中断集中在单核、RSS/RPS 没配好。先看现象、再找根因、最后才动参数,这个顺序不能反。

一、硬中断与软中断的分工

网卡收到一个数据包,第一步是触发硬件中断(IRQ),通知 CPU"有活了"。这一步必须极快,因为中断会打断当前正在跑的任务,而且中断期间通常会屏蔽同级中断。如果在内核的硬中断处理函数里做全部收包工作,中断风暴会直接把系统拖死。

于是 Linux 把中断处理拆成两半:

  • 上半部(硬中断):只做最紧急的登记工作,比如记录网卡状态、把数据包挂到队列,然后尽快返回。
  • 下半部(软中断):真正的重活,比如协议栈解析、送到 socket 缓冲区、唤醒等待的应用进程。

Linux 里的软中断类型可以在 /proc/softirqs 看到,常见的有 NET_RX(收包)、NET_TX(发包)、TIMER(定时器)、RCU、SCHED、TASKLET。网络相关的软中断几乎都记在 NET_RX / NET_TX 上,这也是排查的重点。

软中断的执行时机有三个:硬中断返回前顺带处理、ksoftirqd 内核线程里处理、以及某些显式调用的地方。当软中断负载很重时,内核就把它交给 ksoftirqd/ 内核线程,每个 CPU 核有一个。所以在 top 里看到 ksoftirqd/0 这类线程吃满某个核,就说明该核的软中断压力很大。

二、软中断是怎么失控的

理解失控的原因,才能对症下药。常见情况有这么几类:

触发场景 机理 典型特征
流量突增/正常高并发 收包量激增,单核处理不过来 NET_RX 高,多核均匀上涨
DDoS 或扫描攻击 大量小包、无效连接涌入 单核 si 打满,ss 里 SYN_RECV 暴增
网卡中断只落在 CPU0 未开启多队列或中断亲和性配置不当 只有 CPU0 的 si 高,其余核很闲
大量短连接 频繁建连断连,socket 创建销毁开销大 NET_RX+NET_TX 同高,cs 也高
定时器密集任务 大量 timerfd/epoll_wait 超时 TIMER 高而非网络软中断
虚拟化/网卡驱动问题 收包路径低效、单队列 特定网卡型号下重现

一个很典型的现象是"单核打满":服务器 32 核,但只有 CPU0 的软中断飙到 100%,其他核都闲着。这通常是因为网卡中断没做多队列分发,所有硬中断都落在 CPU0,软中断也跟着堵在一个核上。解决方向是开多队列、配 RSS/RPS、调整中断亲和性。

三、用工具把软中断看清楚

第一层:看总量和分布。

# top 里看 si 列,按 1 展开逐核
top
# 按 1 之后可以看到每个 CPU 的 si/us/sy 分布,判断是不是单核打满

# mpstat 逐核统计,si 列就是软中断占比
mpstat -P ALL 1 5
# 需要 sysstat:
# RHEL/CentOS/Rocky
dnf install -y sysstat
# Debian/Ubuntu
apt install -y sysstat

第二层:看是哪一类软中断在作怪。

# 累计计数,隔几秒采两次做差值
cat /proc/softirqs
# 示例输出(节选):
#                     CPU0       CPU1       CPU2       CPU3
#       NET_RX: 1234567890    2345       3456       4567
#       NET_TX:   23456789    1234       2345       3456
# 若 NET_RX 在 CPU0 远高于其他核,说明收包集中在这一个核

# 采两次差值更直观(间隔 5 秒)
cat /proc/softirqs > /tmp/si1
sleep 5
cat /proc/softirqs > /tmp/si2
# 用脚本或肉眼对比 NET_RX 每核的增量

第三层:看内核线程和中断分布。

# 看 ksoftirqd 线程占用的 CPU
ps -eo pid,comm,psr,%cpu | grep ksoftirqd
# PSR 那一列显示它跑在哪个核上

# 查看网卡硬中断的分布
cat /proc/interrupts | grep -i eth
# 也可以看特定网卡的队列中断
grep -i "your-nic-name" /proc/interrupts

第四层:历史趋势,判断是突发还是长期。

# sar 可以查历史软中断(需 sysstat 并开启 sa 采样)
sar -I SUM 1 5           # 当前软中断汇总
sar -I SUM -f /var/log/sa/sa12   # 查 12 号的历史数据

四、定位与优化实践

思路一:确认是否单核瓶颈。
mpstat -P ALL 1 如果发现只有一个核 si 接近 100%,就是中断分配不均。解决办法是开启网卡多队列并让中断分散到多核:

# 查看网卡支持多少队列
ethtool -l eth0

# 设置队列数(需要网卡和驱动支持,值不能超过最大支持数)
ethtool -L eth0 combined 8

# 查看当前中断亲和性
cat /proc/irq//smp_affinity_list

# 手工把中断绑到指定核(示例:把 IRQ 128 绑到 CPU2)
echo 2 > /proc/irq/128/smp_affinity_list

思路二:启用 RPS/RFS 软件分流。
如果网卡队列有限,可以用 RPS 让内核在软件层把收包分散到多核。注意 RPS 本身会增加一点 CPU 开销,是权衡。

# 查看网卡队列的 RPS 配置
ls /sys/class/net/eth0/queues/
cat /sys/class/net/eth0/queues/rx-0/rps_cpus

# 给 rx-0 队列设置可用 CPU 掩码(0xf 表示前 4 个核)
echo f > /sys/class/net/eth0/queues/rx-0/rps_cpus

# RFS 需要先设置流表大小
echo 32768 > /proc/sys/net/core/rps_sock_flow_entries
echo 4096 > /sys/class/net/eth0/queues/rx-0/rps_flow_cnt

# 永久生效:RHEL/CentOS/Rocky 写入 /etc/rc.local 或 systemd 服务
# Debian/Ubuntu 可写入 /etc/network/if-up.d/ 下的脚本

风险提示:ethtool -L 修改队列数会重置网卡队列,可能短暂中断网络。远程操作前务必确认有带外管理(VNC/IPMI)通道,或先用不影响连接的方式验证。回滚只需重新执行 ethtool -L eth0 combined <原值>。修改 /proc/sys 下参数重启即失效,属临时改动。

思路三:应对攻击型流量。
如果 NET_RX 高且 ss -s 里出现大量 SYN-RECV,多半在被扫描或 SYN Flood。先确认是不是攻击:ss -s 看连接状态分布,ss -ant | awk '{print $1}' | sort | uniq -c 统计状态。防御手段包括启用 syncookies、限制连接速率、上云 WAF/高防。

# 查看连接状态汇总
ss -s

# 临时开启 SYN cookies(默认一般已开启)
sysctl net.ipv4.tcp_syncookies

思路四:减少无谓的网络开销。
把不必要的服务停掉、用 ss -lntup 检查监听端口、排查有没有程序在疯狂轮询网络。有时一个配置错误的应用(比如心跳间隔设成 1 毫秒)就能制造大量软中断。

五、几个容易踩的坑

第一,把 si 高误判为 CPU 不够。加核在单核中断瓶颈下无效,因为中断可能始终落在 CPU0。得先看分布。

第二,忽略虚拟化差异。云主机上 mpstat 看到的核数与宿主机不完全对应,/proc/softirqs 也可能反映的是宿主机视角,跨虚机比较时要谨慎。

第三,参数乱调。net.core.netdev_max_backlog、net.core.somaxconn、netdev_budget 这些能影响收包行为,但改大了可能增加单次处理时长、拉高延迟。每次只改一个,记录原值,观察效果。

# 查看与网络收包相关的关键参数
sysctl net.core.netdev_max_backlog
sysctl net.core.somaxconn
sysctl net.core.dev_weight
# 查看 netdev_budget(每次软中断最多处理多少包)
sysctl net.core.netdev_budget

六、软中断与多队列网卡的配合关系

最后把优化思路串起来。现代网卡基本都支持多队列(multi-queue),每个队列对应一组独立的中断,内核可以针对不同队列分别绑定不同的 CPU 核来处理。理想状态下,收包的中断和软中断应该均匀散布在多核上,而不是让某一个核孤军奋战。

但默认配置未必会这么做。有的系统默认把所有队列中断都放在 CPU0,有的虽然开了多队列却因为 BIOS 里没关掉中断合并(Interrupt Coalescing)或者队列数设得太少,导致分流效果打折。所以排查软中断不能只看 /proc/softirqs 的绝对值,还要结合 ethtool -l(看队列数)和 /proc/interrupts(看中断落核)一起判断。

中断合并是把多个小包攒起来一次性通知 CPU,好处是减少中断次数、降低软中断压力,代价是增加单包延迟。对高吞吐、能容忍几十微秒延迟的业务(比如文件传输、日志汇总),可以适度调大合并参数;对延迟敏感的业务(比如实时交易、游戏对战),则应该把合并关小甚至关掉,宁可多一些中断换低延迟。

# 查看当前中断合并参数
ethtool -c eth0
# 关闭中断合并(低延迟场景,会增加软中断压力)
ethtool -C eth0 rx-usecs 0 tx-usecs 0
# 调大合并间隔(高吞吐场景,降低中断压力)
ethtool -C eth0 rx-usecs 32 rx-frames 32

风险提示:修改合并参数会改变网卡行为,可能影响吞吐或延迟,远程操作时务必先确认带外通道可用。回滚只需按原值重新执行一遍 ethtool -C,或重启网卡驱动恢复默认。

常见问题

Q:si 高但网络流量并不大,可能是什么原因?
可能是小包特别多(包数比带宽更能决定软中断压力)、也可能是定时器软中断(TIMER)而非网络。用 /proc/softirqs 区分是 NET_RX 还是其他类型,再顺着查。

Q:软中断高和上下文切换高是一回事吗?
不是。软中断是内核处理中断下半部,上下文切换是任务调度切换。但两者会互相影响:收包后唤醒应用进程会带来切换,大量短连接会同时抬高两者。

Q:为什么 32 核机器上有核闲着,软中断还高?
这是典型的中断分配不均。硬中断默认可能都落在 CPU0,RSS/RPS/中断亲和性没配好。用 mpstat -P ALL 确认后,调整中断绑定或开启多队列即可。

Q:改 smp_affinity 会不会影响现有连接?
一般不会中断已有连接,因为只是把后续中断引导到别的核。但如果写错了掩码可能影响网卡性能。执行前记录原值,异常时写回即可。

Q:云服务器上能改网卡中断亲和性吗?
部分可以,部分被虚拟化层屏蔽。先试 ethtool -l eth0 看队列数,再看 /proc/interrupts 是否有可操作的中断号。受限时改用 RPS 软件分流。

总结

软中断高于应用无关,根因通常在网络收包路径。第一步是判断它是 CPU 瓶颈还是流量问题,第二步是看分布是否均衡,第三步才考虑调参。

工具链要固定:mpstat -P ALL 1 看逐核 si 分布,/proc/softirqs 看 NET_RX 是哪一类,/proc/interrupts 和 ethtool -l 看中断与队列,sar -I 看历史趋势。

单核打满优先查中断亲和性与网卡多队列,用 ethtool -L 和 smp_affinity 分流;队列不足时上 RPS/RFS 软件分流。

遇到流量型攻击(SYN Flood、扫描),先确认再防御,开启 SYN cookies、限速或用云端防护,不要一味调内核参数。

所有涉及网卡队列、中断绑定、内核参数的改动都要先记录原值、留好回滚,远程操作前确保有带外通道能救回来。