网卡收到数据包后,需要通过硬中断通知CPU来处理。默认情况下,很多中断请求都落在CPU0上,这在低负载时没有明显问题,但一旦流量上来,CPU0可能被软中断和硬中断占满,其他核心却处于空闲状态。要解决这个问题,就需要调整中断亲和性,把不同的IRQ处理任务分配到不同的CPU核心上。

查看当前中断分布与亲和性设置
在动手调整之前,先要弄清楚系统里每个中断当前由哪个核心处理。Linux把所有硬件中断的信息暴露在/proc/interrupts文件中,每一行代表一个IRQ,每一列对应一个CPU核心,数字表示该核心处理该中断的累计次数。
执行cat /proc/interrupts可以看到类似下面的输出。对于网卡,通常以eth0、ens33之类的设备名作为标识,后面的数字就是中断号。如果某个核心的计数远远高于其他核心,就说明中断集中在该核心上,存在优化空间。多队列网卡会为每个队列分配独立的IRQ,常见名称如eth0-TxRx-0、eth0-TxRx-1等。
cat /proc/interrupts
CPU0 CPU1 CPU2 CPU3
0: 34 0 0 0 IO-APIC 2-edge timer
8: 1 0 0 0 IO-APIC 8-edge rtc0
16: 33421 0 0 0 IO-APIC 16-fasteoi ehci_hcd:usb1
24: 1023481 12 9 11 PCI-MSI 524288-edge eth0-TxRx-0
25: 12 1203948 10 8 PCI-MSI 524289-edge eth0-TxRx-1
26: 9 8 1102832 13 PCI-MSI 524290-edge eth0-TxRx-2
27: 11 10 9 1293842 PCI-MSI 524291-edge eth0-TxRx-3每个IRQ对应的亲和性存放在/proc/irq/中断号/smp_affinity和/proc/irq/中断号/smp_affinity_list两个文件中。smp_affinity用十六进制位掩码表示允许处理该中断的CPU集合,smp_affinity_list用十进制列表表示,例如0-3代表CPU0到CPU3都可以处理。查看IRQ 24的当前设置:
cat /proc/irq/24/smp_affinity cat /proc/irq/24/smp_affinity_list
如果smp_affinity显示为ffffffff,说明所有核心都允许处理该中断;如果显示为00000001,说明只有CPU0可以处理。在调整之前,记录这些原始值是个好习惯,方便回滚。
计算CPU掩码并绑定中断到指定核心
把IRQ绑定到特定核心需要计算十六进制掩码。掩码的每一位对应一个CPU,从右往左分别是CPU0、CPU1、CPU2……例如只允许CPU2处理中断,掩码是二进制0100,换算成十六进制为4;允许CPU0和CPU3处理,掩码是二进制1001,换算成十六进制为9。
对于较新的内核,可以直接使用smp_affinity_list文件,用更直观的十进制核心号或范围来设置,省去手动换算的麻烦。例如把IRQ 24绑定到CPU2,可以执行:
echo 2 > /proc/irq/24/smp_affinity_list # 或者使用十六进制掩码 echo 4 > /proc/irq/24/smp_affinity
多队列网卡一般有多个IRQ,建议把每个队列中断依次绑定到不同核心,让流量处理均匀分布。假设一台4核机器上有4个队列中断,IRQ号分别为24、25、26、27,可以这样操作:
echo 0 > /proc/irq/24/smp_affinity_list echo 1 > /proc/irq/25/smp_affinity_list echo 2 > /proc/irq/26/smp_affinity_list echo 3 > /proc/irq/27/smp_affinity_list
设置后再次查看/proc/interrupts,运行一段时间后各核心的中断计数应该趋于均衡。需要注意的是,如果网卡开启了RSS(接收端缩放)或RPS(接收包导向),中断虽然绑定在固定核心,但实际数据处理可能被软中断分发到其他核心,这是正常的,不影响硬中断的优化效果。
处理irqbalance服务与持久化配置
很多发行版默认开启了irqbalance服务,它会根据系统负载动态调整中断亲和性,可能把你手动设置的绑定关系覆盖掉。如果希望中断绑定固定不变,需要先停止并禁用irqbalance。在systemd系统中执行:
systemctl stop irqbalance systemctl disable irqbalance
要确认irqbalance是否真的在干扰,可以设置绑定后等待几秒钟,再次查看smp_affinity_list是否被改变。如果被改回默认值,说明服务还在运行。对于CentOS 7/8、Ubuntu 16.04及之后的版本,都可以用上面的systemctl命令管理。个别老系统可能使用service irqbalance stop来停止。
手动通过echo写入设置的方式在重启后会丢失,需要把命令写入启动脚本。推荐使用rc.local或者systemd的service单元来持久化。也可以把绑定逻辑写成一个shell脚本,放在/etc/rc.d/rc.local中,并给脚本添加执行权限。注意要先确定IRQ号在不同重启后保持不变,对于PCIe网卡通常是稳定的,但USB网卡可能会变化,建议在脚本里动态获取IRQ号。
#!/bin/bash
# 持久化网卡中断绑定
# 获取eth0所有队列的IRQ号
irqs=$(grep eth0 /proc/interrupts | awk -F: '{print $1}')
cpu=0
for irq in $irqs; do
echo $cpu > /proc/irq/$irq/smp_affinity_list
cpu=$((cpu + 1))
done对于使用NetworkManager的系统,也可以在网卡启动后的dispatcher脚本中执行绑定,这样在网卡重新加载后设置依然生效。不过无论用哪种方式,都要保证关闭irqbalance,否则所有持久化努力都可能被覆盖。
验证优化效果与注意事项
绑定完成后,需要通过实际流量测试来验证效果。可以使用iperf3打满网卡带宽,同时观察/proc/interrupts和top命令中的%si(软中断占用率)。如果中断均匀分布在各个核心,且单核软中断占用率下降,说明调整有效。也可以使用mpstat -P ALL 1命令实时查看每个核心的软中断情况。
调整中断亲和性时,不要把某个IRQ绑定到已经被其他高负载任务长期占用的核心上。例如CPU0通常还承担定时器、调度器等工作,把网卡中断绑定到CPU0并不是最优选择。对于NUMA架构的服务器,还要考虑网卡所在的NUMA节点,尽量把中断绑定到同一节点内的CPU,避免跨节点内存访问带来的延迟。
单队列网卡只有一个IRQ,无法像多队列那样把中断分散到多个核心。不过可以配合RPS技术,通过软件方式把数据包处理分散到多个核心,弥补单队列的不足。RPS的配置在/sys/class/net/eth0/queues/rx-0/rps_cpus文件中,同样是十六进制掩码,例如设置成f表示允许所有核心参与处理。硬中断本身仍然集中,但后续的协议栈处理被分散开了。
中断亲和性IRQ绑定smp_affinity修改时间:2026-09-28 19:06:46