4. 中断亲和性(IRQ Affinity):SMP IRQ Affinity 原理与配置

在多核(SMP)系统中,中断处理器的选择直接影响系统的实时性与吞吐量。中断亲和性(IRQ Affinity)允许将特定的硬件中断绑定到指定的 CPU 核心上,从而避免中断在不同核心间迁移带来的缓存污染和调度延迟。本章节深入剖析 SMP IRQ Affinity 的内核实现原理,并详细讲解通过 /proc/irq/ 接口进行配置的具体方法。

4.1 SMP IRQ Affinity 原理

在对称多处理(SMP)架构下,每个 CPU 核心都拥有独立的中断控制器接口(如 APIC)。内核通过中断分发机制决定将某个 IRQ 送达哪个核心。IRQ Affinity 的核心思想是:通过设置 CPU 位掩码(cpumask),限制特定中断只能由掩码中指定的核心处理。

核心收益:
  • 减少缓存抖动:中断处理程序及其访问的数据结构始终在同一核心上运行,L1/L2 缓存命中率显著提升。
  • 降低调度延迟:避免中断在多个核心间“乒乓”迁移,减少上下文切换和 TLB 刷新开销。
  • 隔离关键负载:将高频率中断(如网卡 RX/TX)绑定到专用核心,保障业务线程在其余核心上的稳定运行。

内核中,每个 IRQ 描述符(struct irq_desc)都包含一个 irq_affinity 字段,类型为 struct cpumask。当硬件中断触发时,中断控制器驱动(如 IO-APIC、MSI)会查询该掩码,并通过 IPI(核间中断)或直接路由将中断信号发送到掩码中优先级最高的在线核心。

SMP IRQ Affinity 中断分发原理 硬件设备 中断控制器 (IO-APIC/MSI) CPU0 (允许) CPU1 (禁止) CPU2 (禁止) CPU3 (允许) 说明: 中断控制器根据 IRQ 的 cpumask 位掩码(如 0b1001)将中断仅路由到 CPU0 和 CPU3。 CPU1 和 CPU2 即使空闲也不会收到该 IRQ,从而避免核心间缓存迁移。

4.2 /proc/irq/ 接口详解

Linux 内核通过 /proc/irq/ 虚拟文件系统暴露每个中断号的状态与配置接口。每个注册的 IRQ 在 /proc/irq/ 下都有一个以中断号命名的目录。

文件/目录 类型 说明
/proc/irq/{N}/smp_affinity 可读写 存储 IRQ N 的 CPU 位掩码(十六进制),写入新掩码即动态调整亲和性
/proc/irq/{N}/smp_affinity_list 只读 以 CPU 列表形式(如 0-3,5)显示当前亲和性,便于人类阅读
/proc/irq/{N}/node 只读 显示该中断所属的 NUMA 节点编号,用于跨 NUMA 优化
/proc/irq/{N}/actions 只读 列出注册到该 IRQ 的所有设备驱动名称
/proc/interrupts 只读 全局中断统计表,显示每个 CPU 上每个 IRQ 的中断计数
实践建议:在调整亲和性前,先通过 cat /proc/interrupts | grep {irq_number} 观察当前中断在各核心上的分布情况,确认是否存在不均衡。

4.3 设置中断亲和性的方法(echo mask)

设置 IRQ Affinity 的核心操作是向 /proc/irq/{N}/smp_affinity 写入十六进制 CPU 位掩码。位掩码的每一位对应一个 CPU 核心:最低位(bit 0)代表 CPU0,bit 1 代表 CPU1,以此类推。

4.3.1 位掩码计算规则

目标 CPU 二进制掩码 十六进制掩码 说明
仅 CPU0 0000 0001 1 最低位置 1
仅 CPU1 0000 0010 2 bit 1 置 1
CPU0 + CPU1 0000 0011 3 bit 0 和 bit 1 置 1
CPU0 + CPU2 0000 0101 5 bit 0 和 bit 2 置 1
所有 CPU(4核) 0000 1111 f 低 4 位全 1
CPU2 + CPU3(4核) 0000 1100 c bit 2 和 bit 3 置 1

4.3.2 标准配置步骤

  1. 确认中断号:通过 cat /proc/interrupts 找到目标设备对应的 IRQ 号(第一列)。
  2. 查看当前亲和性:
    cat /proc/irq/{N}/smp_affinity
    cat /proc/irq/{N}/smp_affinity_list
  3. 写入新掩码:
    echo {hex_mask} > /proc/irq/{N}/smp_affinity

    例如,将 IRQ 130 绑定到 CPU0 和 CPU3:

    echo 9 > /proc/irq/130/smp_affinity

    (二进制 1001 → CPU0 和 CPU3)

  4. 验证生效:
    cat /proc/irq/{N}/smp_affinity
    # 观察 /proc/interrupts 中该 IRQ 的中断计数是否只在目标 CPU 上增长
注意事项:
  • 写入 smp_affinity 需要 root 权限。
  • 对于 MSI-X 中断,每个队列可能有独立的亲和性设置,需通过 /proc/irq/{N}/smp_affinity 分别配置。
  • 部分老旧驱动或中断控制器可能不支持运行时动态修改,修改后需检查是否生效。
  • 在 NUMA 系统中,建议将中断绑定到与设备所在 PCIe slot 同节点的 CPU 上,以降低跨 NUMA 访问延迟。

4.3.3 持久化配置

通过 echo 写入的方式在系统重启后会丢失。如需持久化,可使用以下方法之一:

  • irqbalance 服务:配置 /etc/sysconfig/irqbalance 或 /etc/irqbalance.conf,通过 --banirq 或 --hintpolicy 策略实现。
  • udev 规则:在 /etc/udev/rules.d/ 中创建规则,匹配设备后执行 echo mask > /proc/irq/{N}/smp_affinity。
  • 系统服务脚本:在 /etc/rc.local 或 systemd 服务中写入配置命令。
中断亲和性配置流程 步骤1 cat /proc/interrupts 步骤2 确定 IRQ 号 步骤3 计算十六进制掩码 步骤4 echo mask > smp_affinity 验证:cat /proc/interrupts 检查计数分布 确认中断仅出现在目标 CPU 上 提示:对于 MSI-X 多队列设备(如现代网卡),每个队列对应独立 IRQ,需逐一配置。 使用 cat /proc/irq/{N}/smp_affinity_list 可快速确认当前绑定关系。

4.4 实战案例:绑定网卡中断到指定核心

假设系统有 4 个 CPU 核心,网卡(eth0)使用 MSI-X 中断,IRQ 号为 130-133。目标是将所有网卡中断绑定到 CPU2 和 CPU3(掩码 0b1100 = 0xc)。

# 1. 查看当前中断分布
cat /proc/interrupts | grep eth0

# 2. 逐个绑定 IRQ 130-133
echo c > /proc/irq/130/smp_affinity
echo c > /proc/irq/131/smp_affinity
echo c > /proc/irq/132/smp_affinity
echo c > /proc/irq/133/smp_affinity

# 3. 验证绑定结果
cat /proc/irq/130/smp_affinity
# 输出应为: c

# 4. 观察中断计数变化(在高流量下测试)
watch -n 1 'cat /proc/interrupts | grep eth0'
性能预期:在正确绑定后,CPU0 和 CPU1 上的网卡中断计数应降为 0,所有中断处理集中在 CPU2 和 CPU3 上。业务线程若绑定在 CPU0-1 上运行,将不再被网卡中断抢占,调度延迟可降低 30%-50%(具体取决于中断频率和负载)。