4. 中断亲和性(SMP IRQ Affinity)原理
中断亲和性(SMP IRQ Affinity)是 Linux 内核在多核(SMP)系统中控制中断分发策略的核心机制。它通过一组 CPU 位掩码(cpumask)来决定某个中断请求(IRQ)可以被哪些 CPU 核心处理。合理配置中断亲和性,是驱动调优中实现负载均衡、降低尾延迟、提升缓存命中率的关键手段。
4.1 CPU 位掩码(cpumask)基础
CPU 位掩码是一个二进制位图,每一位代表一个逻辑 CPU 核心。位值为 1 表示该 CPU 被选中,位值为 0 表示排除。内核和用户空间通过两种格式交互:
| 格式 | 示例 | 说明 |
|---|---|---|
| 十六进制掩码 | 0x00000003 |
每 4 位对应一个十六进制字符,从低位到高位映射 CPU0、CPU1…… |
| 列表格式 | 0-2,5 |
逗号分隔的 CPU 编号或范围,更直观易读。 |
位掩码映射规则: 以 8 核系统为例,掩码
0xff 表示所有 CPU(位 0~7 全 1);0x11(二进制 00010001)表示仅允许 CPU0 和 CPU4。
内核中 cpumask 的典型操作包括:
- 设置亲和性: 将 IRQ 绑定到指定 CPU 集合。
- 查询当前绑定: 读取 /proc/irq/ 下的对应文件。
- 动态调整: 在运行时修改掩码,无需重启驱动或系统。
4.2 /proc/irq/ 目录结构
Linux 内核通过 /proc/irq/ 伪文件系统暴露每个中断号的控制接口。每个注册的中断号对应一个子目录,目录名即为中断号。典型结构如下:
/proc/irq/
├── 28
│ ├── affinity_hint # 内核或驱动建议的亲和性(只读)
│ ├── effective_affinity # 当前实际生效的 CPU 掩码(只读)
│ ├── node # 中断所属的 NUMA 节点
│ ├── smp_affinity # 用户可写的 CPU 位掩码(十六进制)
│ ├── smp_affinity_list # 用户可写的 CPU 位掩码(列表格式)
│ └── spurious # 统计虚假中断次数
├── 29
│ └── ...
└── default
└── smp_affinity # 新注册中断的默认亲和性
关键文件说明:
smp_affinity:十六进制掩码,写入立即生效。smp_affinity_list:列表格式掩码,与 smp_affinity 互相同步。effective_affinity:只读,反映内核实际使用的掩码(可能受中断控制器限制)。affinity_hint:驱动提供的建议值,用户空间可参考但非强制。
4.3 smp_affinity 与 smp_affinity_list 文件
这两个文件是调整中断亲和性的主要入口。它们指向同一个内核 cpumask 变量,只是用户接口格式不同:
| 属性 | smp_affinity | smp_affinity_list |
|---|---|---|
| 格式 | 十六进制数(如 00000003) |
逗号分隔列表(如 0-1,3) |
| 可读性 | 适合脚本解析,位操作直观 | 适合人工阅读和手动配置 |
| 写入方式 | echo 0x03 > /proc/irq/28/smp_affinity |
echo 0-1 > /proc/irq/28/smp_affinity_list |
| 同步关系 | 写入一个文件,另一个自动更新 | 写入一个文件,另一个自动更新 |
常见陷阱:
- 写入 smp_affinity 时,十六进制数不要带
0x前缀,直接写00000003即可。 - 如果写入的掩码包含不存在的 CPU(如 8 核系统写入 CPU8),内核会静默忽略无效位,不会报错。
- 某些中断控制器(如 MSI-X)可能不支持任意 CPU 绑定,此时 effective_affinity 可能与 smp_affinity 不一致。
4.4 中断亲和性工作流程
下图展示了从用户空间写入亲和性掩码到中断控制器实际分发中断的完整链路:
4.5 实战操作示例
以下命令演示如何将 IRQ 28 绑定到 CPU0 和 CPU1:
# 查看当前亲和性(十六进制)
cat /proc/irq/28/smp_affinity
# 输出示例:00000001(仅 CPU0)
# 查看当前亲和性(列表格式)
cat /proc/irq/28/smp_affinity_list
# 输出示例:0
# 绑定到 CPU0 和 CPU1(十六进制方式)
echo 00000003 > /proc/irq/28/smp_affinity
# 绑定到 CPU0 和 CPU1(列表方式)
echo 0-1 > /proc/irq/28/smp_affinity_list
# 验证生效
cat /proc/irq/28/effective_affinity
# 输出示例:00000003
驱动调优建议:
- 对于高吞吐网卡驱动,将 RX/TX 队列中断分别绑定到不同 CPU,避免单核瓶颈。
- 对于 NUMA 系统,优先将中断绑定到与设备同节点的 CPU,减少跨内存访问延迟。
- 使用
irqbalance服务时,手动设置的亲和性可能被覆盖,需关闭或配置白名单。
4.6 内核实现要点
从驱动开发角度看,中断亲和性的内核实现涉及以下关键数据结构与函数:
| 组件 | 文件/函数 | 作用 |
|---|---|---|
| irq_desc | kernel/irq/irqdesc.c | 每个中断号对应的描述符,内含 irq_affinity 成员 |
| irq_chip | include/linux/irq.h | 中断控制器抽象,提供 irq_set_affinity 回调 |
| proc 接口 | kernel/irq/proc.c | 实现 smp_affinity 和 smp_affinity_list 的读写逻辑 |
| cpumask 操作 | include/linux/cpumask.h | 提供 cpumask_set_cpu、cpumask_clear 等宏 |
驱动开发者通常不需要直接操作这些接口,但理解其原理有助于在编写 request_irq 或 irq_set_affinity_hint 时做出更优的决策。