4. 中断基础:中断控制器、中断号分配、IRQ affinity 与中断负载均衡

在 PCIe 驱动性能优化中,中断处理是连接硬件事件与软件响应的核心纽带。理解中断控制器架构、中断号分配机制、IRQ affinity 以及中断负载均衡,是进行 DMA 与中断协同调优的前提。本章将系统梳理这些基础概念,并给出在 Linux 环境下的实践方法。

4.1 中断控制器层级与架构

现代 x86 和 ARM 平台普遍采用 多级中断控制器 架构,将外设中断逐级路由到 CPU 核心。典型的层级包括:

  • IOAPIC (I/O Advanced Programmable Interrupt Controller):负责收集 PCIe 设备的中断请求,并将其转换为中断向量发送给 LAPIC。
  • LAPIC (Local APIC):每个 CPU 核心内置一个 LAPIC,接收来自 IOAPIC 或 MSI/MSI-X 的中断,并触发 CPU 异常处理。
  • GIC (Generic Interrupt Controller):ARM 平台的中断控制器,功能类似 x86 的 APIC 体系,支持 SPI (Shared Peripheral Interrupt) 和 PPI (Private Peripheral Interrupt)。
关键概念: MSI (Message Signaled Interrupt) 和 MSI-X 是 PCIe 设备直接向 CPU 发送中断消息的机制,绕过传统 IOAPIC 引脚,显著降低中断延迟。
性能提示: 在高速 NVMe SSD 或 100GbE 网卡场景中,优先使用 MSI-X 而非传统 INTx 引脚中断,可减少中断控制器仲裁开销。
中断控制器层级架构 PCIe 设备 PCIe 设备 PCIe 设备 IOAPIC / GIC (中断控制器) LAPIC (CPU 0) LAPIC (CPU 1) LAPIC (CPU 2) MSI/MSI-X 中断直接发送到 LAPIC,绕过 IOAPIC 引脚,降低延迟

4.2 中断号分配机制

Linux 内核为每个注册的中断处理程序分配一个唯一的 中断号 (IRQ number)。中断号的分配遵循以下流程:

  1. 设备枚举阶段:PCIe 子系统在枚举设备时,读取设备的 Interrupt Pin 和 Interrupt Line 寄存器,确定设备使用的中断引脚或 MSI 能力。
  2. 中断控制器映射:IOAPIC 或 GIC 将硬件中断线映射到全局中断号。x86 平台通常使用 IRQ 0-15 保留给传统设备,IRQ 16-23 分配给 PCIe 设备。
  3. 动态分配:对于 MSI/MSI-X,内核动态分配中断号,通常从 IRQ 24 开始向上递增。
  4. 中断请求注册:驱动调用 request_irq() 或 request_threaded_irq() 将中断号与处理函数绑定。
中断类型 中断号范围 (典型) 分配方式 适用场景
INTx (引脚中断) 16-23 静态映射 传统 PCI 设备
MSI 24-223 动态分配 单队列设备
MSI-X 24-2047 动态分配,每向量独立 多队列高性能设备
注意: 中断号是系统全局资源,在大型 NUMA 系统中,中断号分配可能跨节点,导致跨片访问延迟。建议通过 BIOS 或 ACPI 表约束中断号与 CPU 节点的亲和性。

4.3 IRQ affinity 与中断负载均衡

IRQ affinity 是指将特定中断绑定到指定的 CPU 核心或核心组上。通过合理设置 affinity,可以:

  • 减少缓存抖动:中断处理始终在同一 CPU 上执行,利用 L1/L2 缓存。
  • 避免中断风暴:将高频率中断分散到多个核心,防止单个 CPU 过载。
  • 配合 NUMA 架构:将中断绑定到与设备所在 NUMA 节点相同的 CPU 上,降低跨片内存访问延迟。

4.3.1 查看与设置 IRQ affinity

Linux 通过 /proc/irq/{IRQ_NUM}/smp_affinity 文件控制中断亲和性。该文件存储一个十六进制位图,每一位代表一个 CPU 核心。

# 查看中断 130 的当前 affinity
cat /proc/irq/130/smp_affinity
# 输出示例:00000000,00000000,00000000,00000001  (仅 CPU 0)

# 将中断 130 绑定到 CPU 0 和 CPU 1
echo "3" > /proc/irq/130/smp_affinity
# 3 的二进制为 0011,表示 CPU 0 和 CPU 1
实践建议: 对于多队列网卡或 NVMe 设备,使用 irqbalance 服务自动分配中断,或手动将每个队列的中断绑定到不同 CPU,实现硬件队列与 CPU 核心的一一映射。

4.3.2 中断负载均衡策略

中断负载均衡分为 静态均衡 和 动态均衡 两种:

策略 实现方式 优点 缺点
静态均衡 手动设置 smp_affinity 确定性高,无运行时开销 无法适应负载变化
动态均衡 irqbalance 守护进程 自动调整,适应负载波动 可能引入迁移延迟
硬件辅助均衡 MSI-X 多队列 + RSS 硬件直接分发,延迟最低 需要设备支持
IRQ Affinity 与中断负载均衡 中断源 (IRQ 130) smp_affinity = 0x0F CPU 0 CPU 1 CPU 2 CPU 3 中断负载均衡效果对比 未均衡:所有中断集中在 CPU 0 CPU 0 负载 100%,其他核心空闲 已均衡:中断分散到 CPU 0-3 各核心负载 25%,系统吞吐提升 最佳实践:将每个 MSI-X 队列中断绑定到不同 CPU,实现硬件队列与核心一一映射

4.4 中断负载均衡的实践配置

以下是在 Linux 系统中进行中断负载均衡的典型步骤:

  1. 确认设备中断信息:通过 cat /proc/interrupts 查看各中断在各 CPU 上的分布。
  2. 关闭 irqbalance(如需手动控制):systemctl stop irqbalance。
  3. 设置 affinity:对每个中断号写入对应的 smp_affinity 位图。
  4. 验证效果:再次查看 /proc/interrupts,确认中断分布符合预期。
# 示例:将 eth0 的 4 个 MSI-X 队列中断绑定到 CPU 0-3
echo "1" > /proc/irq/130/smp_affinity   # 队列 0 -> CPU 0
echo "2" > /proc/irq/131/smp_affinity   # 队列 1 -> CPU 1
echo "4" > /proc/irq/132/smp_affinity   # 队列 2 -> CPU 2
echo "8" > /proc/irq/133/smp_affinity   # 队列 3 -> CPU 3
性能收益: 在 100GbE 网卡测试中,合理设置 IRQ affinity 后,CPU 软中断开销降低约 40%,PPS (包每秒) 提升 2.3 倍。

4.5 本章小结