4. 中断亲和性(IRQ Affinity)

中断亲和性(IRQ Affinity)是 Linux 内核提供的一种机制,允许系统管理员将特定的硬件中断(IRQ)绑定到指定的 CPU 核心上处理。通过合理配置中断亲和性,可以显著提升多核 CPU 系统的网络处理性能,避免单个 CPU 成为瓶颈。

4.1 核心概念

在默认情况下,中断可以在所有 CPU 核心之间动态分配。这种灵活性虽然能平衡负载,但会带来以下问题:

  • CPU 缓存污染:中断在不同核心间迁移,导致 L1/L2 缓存频繁失效。
  • 锁竞争加剧:多个核心同时处理同一设备的中断,增加自旋锁开销。
  • 延迟不可预测:中断响应时间因核心负载不同而波动。

中断亲和性通过设置 CPU 位掩码(bitmask),将中断固定到特定核心,从而:

  • 提高缓存命中率,减少内存访问延迟。
  • 降低锁竞争,提升多核扩展性。
  • 使中断处理延迟更稳定、可预测。
关键原则:将网卡队列的中断绑定到与处理该队列数据包的应用程序相同的 CPU 核心(或同一 NUMA 节点上的核心),实现“数据包接收 → 中断处理 → 应用处理”全链路本地化。
中断亲和性概念图 中断亲和性:绑定中断到指定 CPU 网卡 eth0 IRQ 请求 中断控制器 IO-APIC smp_affinity 位掩码: 0x02 CPU 0 未绑定 CPU 1 已绑定 ✓ CPU 2 未绑定 CPU 3 未绑定 绑定 中断仅由 CPU 1 处理 其他核心不受影响

4.2 smp_affinity 文件

在 Linux 系统中,每个中断的亲和性设置通过 /proc/irq/{IRQ_NUM}/smp_affinity 文件进行管理。该文件存储一个十六进制位掩码,每一位代表一个 CPU 核心。

文件路径 说明
/proc/irq/{IRQ_NUM}/smp_affinity 存储中断亲和性的十六进制位掩码(如 02 表示仅 CPU 1)
/proc/irq/{IRQ_NUM}/smp_affinity_list 以 CPU 列表形式显示亲和性(如 1 表示 CPU 1)
/proc/interrupts 显示每个中断在每个 CPU 上的处理次数统计

位掩码计算规则

位掩码从最低位(bit 0)开始对应 CPU 0,bit 1 对应 CPU 1,以此类推。常用掩码值如下:

目标 CPU 二进制位掩码 十六进制值
CPU 0 0000 0001 01
CPU 1 0000 0010 02
CPU 2 0000 0100 04
CPU 3 0000 1000 08
CPU 0-3(全部) 0000 1111 0f
CPU 0 和 CPU 2 0000 0101 05
提示:对于 NUMA 架构,建议将中断绑定到与网卡所在 NUMA 节点对应的 CPU 核心上,以避免跨 NUMA 访问带来的额外延迟。可以通过 cat /sys/class/net/eth0/device/numa_node 查看网卡所在的 NUMA 节点。

4.3 手动绑定中断到指定 CPU

手动绑定中断亲和性分为三个步骤:查找中断号、计算位掩码、写入配置文件。以下以绑定网卡 eth0 的接收队列中断到 CPU 1 为例。

步骤 1:查找中断号

通过 /proc/interrupts 查看网卡队列对应的中断号:

# cat /proc/interrupts | grep eth0
  CPU0    CPU1    CPU2    CPU3
 24:   12345       0       0       0   IR-PCI-MSI  eth0-rx-0
 25:       0   23456       0       0   IR-PCI-MSI  eth0-tx-0
 26:       0       0   34567       0   IR-PCI-MSI  eth0-rx-1
 27:       0       0       0   45678   IR-PCI-MSI  eth0-tx-1

从输出可知,eth0-rx-0(接收队列 0)的中断号为 24。

步骤 2:计算位掩码

要将中断绑定到 CPU 1(核心编号从 0 开始),位掩码为 02(二进制 0000 0010)。

步骤 3:写入 smp_affinity 文件

# echo 02 > /proc/irq/24/smp_affinity

验证绑定是否生效:

# cat /proc/irq/24/smp_affinity
02

# cat /proc/irq/24/smp_affinity_list
1

# cat /proc/interrupts | grep "24:"
  CPU0    CPU1    CPU2    CPU3
 24:   12345   23456       0       0   IR-PCI-MSI  eth0-rx-0

可以看到,中断 24 现在仅在 CPU 0 和 CPU 1 上处理(写入 02 后,新中断仅由 CPU 1 处理,但历史计数仍保留在 CPU 0 上)。

手动绑定中断流程 手动绑定中断到指定 CPU 流程 步骤 1 查找中断号 cat /proc/interrupts 步骤 2 计算位掩码 CPU 1 → 0x02 步骤 3 写入配置文件 echo 02 > smp_affinity 验证绑定 cat /proc/interrupts | grep IRQ 注意事项 需 root 权限 重启后失效(非持久化)

4.4 持久化配置

手动写入 /proc/irq/{IRQ_NUM}/smp_affinity 的设置在系统重启后会丢失。要实现持久化,有以下两种常用方法:

方法 配置方式 适用场景
irqbalance 服务 配置 /etc/sysconfig/irqbalance 或 /etc/default/irqbalance,设置 IRQBALANCE_BANNED_CPUS 排除特定核心 需要动态平衡,但希望保留部分核心专用于中断
rc.local / systemd 服务 在系统启动脚本中执行 echo 命令写入 smp_affinity 需要精确控制每个中断的绑定目标
udev 规则 通过 /etc/udev/rules.d/ 中的规则,在设备加载时自动设置亲和性 设备热插拔场景,如 SR-IOV VF 绑定
注意:如果系统同时运行 irqbalance 服务,手动设置的 smp_affinity 可能会被 irqbalance 覆盖。建议在绑定前停止 irqbalance 服务(systemctl stop irqbalance),或通过 IRQBALANCE_BANNED_CPUS 将目标 CPU 排除在动态平衡范围之外。

4.5 多队列网卡的批量绑定

对于多队列网卡(如 ixgbe、mlx5 等),通常每个接收/发送队列对应一个独立的中断号。批量绑定可以按照队列编号与 CPU 核心一一对应的策略进行:

#!/bin/bash
# 批量绑定网卡 eth0 的接收队列中断到不同 CPU
# 假设有 4 个接收队列,绑定到 CPU 0-3

for irq in $(grep "eth0-rx" /proc/interrupts | awk '{print $1}' | sed 's/://'); do
    cpu=$((irq % 4))          # 根据中断号取模分配 CPU
    mask=$(printf '%x' $((1 << cpu)))
    echo $mask > /proc/irq/$irq/smp_affinity
    echo "IRQ $irq → CPU $cpu (mask: $mask)"
done

执行结果示例:

IRQ 24 → CPU 0 (mask: 1)
IRQ 25 → CPU 1 (mask: 2)
IRQ 26 → CPU 2 (mask: 4)
IRQ 27 → CPU 3 (mask: 8)
最佳实践:在 NUMA 系统中,应优先将中断绑定到与网卡同节点的 CPU 核心。例如,如果网卡在 NUMA node 0,则只使用 CPU 0-3(假设 node 0 包含 CPU 0-3),避免跨 NUMA 中断处理带来的额外延迟。

4.6 验证与监控

绑定完成后,可以通过以下命令验证中断分布是否符合预期:

# 查看中断在各 CPU 上的分布
watch -n 1 'cat /proc/interrupts | grep eth0'

# 查看特定中断的亲和性设置
cat /proc/irq/24/smp_affinity
cat /proc/irq/24/smp_affinity_list

# 查看 CPU 的中断处理负载
mpstat -I CPU 1 5

通过 mpstat -I 可以查看每个 CPU 的中断处理时间占比,确保绑定后中断负载集中在目标核心上,而其他核心的中断处理时间趋近于零。