Linux SMP IRQ Affinity 实战指南:用 /proc/irq 精确绑定中断到指定 CPU
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
导读
本文基于 Linux 内核文档 Documentation/core-api/irq/irq-affinity.rst 展开,系统讲解 SMP(对称多处理)系统中如何通过/proc/irq/接口将某个中断源(IRQ)绑定到指定的 CPU 集合,从而优化网络、存储等设备的处理性能与缓存局部性。读完本文,你将掌握smp_affinity、smp_affinity_list与default_smp_affinity三个核心文件的读写方法、位掩码与 CPU 列表两种格式的换算、如何用/proc/interrupts验证绑定效果,以及哪些中断(如 affinity-managed 中断)不能通过用户空间修改。
/proc/irq/ 下的亲和性接口:格式与语义
在 SMP 系统上,内核为每个已注册的中断源在/proc/irq/IRQ#/目录下暴露了亲和性控制文件。核心文档指出,/proc/irq/IRQ#/smp_affinity与/proc/irq/IRQ#/smp_affinity_list用于指定该 IRQ 允许投递到哪些目标 CPU:
smp_affinity:以位掩码形式表示允许的 CPU 集合,每个 bit 对应一个 CPU;smp_affinity_list:以CPU 列表形式(如0-3,8)表示同样的语义,二者读写等价,只是格式不同。
从源码实现看,这两个文件由 kernel/irq/proc.c 在register_irq_proc()中创建,其权限位是动态决定的:仅当irq_can_set_affinity_usr()返回真时才会加上写权限(S_IWUSR),否则该文件只读。而irq_can_set_affinity_usr()的定义在 kernel/irq/manage.c:它在常规检查之外,还要求该中断没有设置IRQD_AFFINITY_MANAGED标志——也就是说,由内核管理的 affinity-managed 中断(详见下文)不允许用户空间改写。
写入时内核分别调用cpumask_parselist_user()(list 格式)或cpumask_parse_user()(位掩码格式)解析输入,见 kernel/irq/proc.c;读取时通过cpumask_pr_args()打印,其中位掩码以十六进制显示(如0000000f)。
使用上有两条硬性约束,文档明确给出:
- 不允许关闭所有 CPU(即不能把亲和性掩码写为全 0);
- 如果中断控制器本身不支持 IRQ 亲和性,那么写入不会生效,文件内容始终停留在默认的“所有 CPU”值。
default_smp_affinity:新激活中断的默认掩码
/proc/irq/default_smp_affinity指定应用于所有非活跃 IRQ的默认亲和性掩码。当某个 IRQ 被分配/激活后,它的亲和性位掩码会被设置成该默认值,之后才可以通过上述两个文件按需修改。内核默认值为0xffffffff,即允许投递到所有 CPU。
该文件由 kernel/irq/proc.c 中的register_default_affinity_proc()在CONFIG_SMP下创建,读写分别对应default_affinity_show()(打印全局变量irq_default_affinity,见 kernel/irq/proc.c)与default_affinity_write()(同样经cpumask_parse_user()解析后更新全局默认掩码,见 kernel/irq/proc.c)。
典型用法:在系统启动早期、大量设备驱动尚未申请中断之前,先设置一个合理的默认掩码,让随后注册的中断自动落到期望的 CPU 集合上,省去逐个调整的麻烦。
实战示例一:把 eth1 的 IRQ44 绑定到 CPU0-3
核心文档给出的是一个 8-CPU SMP 主机上的完整演示。首先进入 IRQ44 的 proc 目录并查看当前亲和性:
[root@moon 44]# cd /proc/irq/44 [root@moon 44]# cat smp_affinity ffffffff初始值为ffffffff,表示允许所有 8 个 CPU(bit0–bit7 全为 1)。接下来把掩码写成0f(二进制00001111,即 bit0–bit3 为 1),把中断限制在前 4 个 CPU:
[root@moon 44]# echo 0f > smp_affinity [root@moon 44]# cat smp_affinity 0000000f为了产生真实的中断负载,示例使用ping -f h(flood ping)对目标主机h打流,然后通过/proc/interrupts观察 IRQ44 在各 CPU 上的计数分布:
[root@moon 44]# cat /proc/interrupts | grep 'CPU\|44:' CPU0 CPU1 CPU2 CPU3 CPU4 CPU5 CPU6 CPU7 44: 1068 1785 1785 1783 0 0 0 0 IO-APIC-level eth1可以看到 IRQ44 只投递到了 CPU0-3(计数非零),CPU4-7 的计数全为 0。这说明亲和性设置立即生效,中断被严格限制在前 4 个处理器上。
实战示例二:切换到 CPU4-7 并验证
接下来把同一 IRQ 改绑到后 4 个 CPU。位掩码f0(二进制11110000)对应 bit4–bit7:
[root@moon 44]# echo f0 > smp_affinity [root@moon 44]# cat smp_affinity 000000f0 [root@moon 44]# ping -f h ... [root@moon 44]# cat /proc/interrupts | grep 'CPU\|44:' CPU0 CPU1 CPU2 CPU3 CPU4 CPU5 CPU6 CPU7 44: 1068 1785 1785 1783 1784 1069 1070 1069 IO-APIC-level eth1这一次,CPU0-3 的计数与上一次完全相同(1068/1785/1785/1783,没有增长),而 CPU4-7 出现了新的增量。这从计数器角度精确印证:中断源在切换后只投递到新的 CPU 集合,旧集合的计数器完全静止。
实战示例三:用 smp_affinity_list 绑定高位 CPU(1024-1031)
当目标 CPU 编号很大时,位掩码会变得冗长而难以阅读。例如要把 IRQ44 限制到 CPU1024-1031,位掩码需要 32 个全 0 的十六进制位段之后才出现有效位,极易出错;此时应改用 CPU 列表格式:
[root@moon 44]# echo 1024-1031 > smp_affinity_list [root@moon 44]# cat smp_affinity_list 1024-10311024-1031表示从 CPU1024 到 CPU1031 的连续区间。核心文档特别提示:同样的目标如果用位掩码表达,需要写出 32 个前置的零位段,可读性与可维护性都很差——这正是smp_affinity_list存在的意义。在脚本或系统调优实践中,涉及高位 CPU 时优先使用 list 格式。
补充:effective_affinity 与只读的 affinity_hint
除了smp_affinity*两个可写文件,/proc/irq/IRQ#/下还有两个关联文件(在CONFIG_GENERIC_IRQ_EFFECTIVE_AFF_MASK与 SMP 配置下创建,见 kernel/irq/proc.c):
effective_affinity/effective_affinity_list:显示中断当前实际生效的 CPU 掩码。由于硬件或中断控制器约束,实际投递目标可能与smp_affinity中设置的允许集合不完全一致,排查“设了却没生效”的问题时应同时对比这两个值;affinity_hint:只读(0444),由驱动提供的建议亲和性,仅供查看参考,不参与内核决策。
在排障实践中,一个常见误区是只cat smp_affinity就断言中断落在某 CPU 上;正确做法是结合effective_affinity与/proc/interrupts的计数器共同确认。
哪些中断改不了:managed IRQ 与 isolcpus
并非所有中断都允许用户空间调整亲和性。内核文档 Documentation/core-api/irq/managed_irq.rst 与 Documentation/core-api/irq/concepts.rst 对相关背景有更完整的阐述:
- Affinity-managed 中断:面向每队列一个中断向量的大型设备(典型如 NVMe 的多 I/O 队列),驱动通过
pci_alloc_irq_vectors_affinity()(配合PCI_IRQ_AFFINITY标志)分配带亲和性约束的向量,IRQ 核心据此将中断尽量均匀地散布到各 CPU。这类中断的亲和性完全由内核管理,用户空间写smp_affinity会被拒绝(源码层面的判定就是 kernel/irq/manage.c 中对IRQD_AFFINITY_MANAGED的检查,对应irq_can_set_affinity_usr()返回 false、proc 文件只读)。当亲和掩码内的 CPU 全部下线时,中断会被关闭,直到有 CPU 重新上线才恢复; - isolcpus=managed_irq,:引导参数,让 managed 中断在分配时尽量避开指定 CPU 掩码。这是一种 best-effort 隔离:如果所有非隔离 CPU 都离线,中断仍会落到被隔离的 CPU 上。由于掩码内 CPU 实际仍属于中断的允许集合,当非隔离 CPU 全部离线而隔离 CPU 在线时,中断会被迁移到隔离 CPU;
- 另外,
irqaffinity=引导参数可为非 managed 中断指定初始亲和集合。
理解这些机制有助于判断:为什么某些设备的中断在/proc/irq/下看起来“写不进去”——那不是权限问题,而是内核主动管理的设计使然。
验证与排障小结
| 操作 | 命令/文件 | 说明 |
|---|---|---|
| 查看允许集合(位掩码) | cat /proc/irq/44/smp_affinity | 十六进制,每 bit 对应一个 CPU |
| 设置允许集合(位掩码) | echo 0f > /proc/irq/44/smp_affinity | 禁止写全 0;需中断控制器支持 |
| 查看允许集合(CPU 列表) | cat /proc/irq/44/smp_affinity_list | 格式如0-3,8 |
| 设置允许集合(CPU 列表) | echo 1024-1031 > /proc/irq/44/smp_affinity_list | 高位 CPU 时更可读 |
| 查看实际生效集合 | cat /proc/irq/44/effective_affinity_list | 实际投递目标,可能不等于允许集合 |
| 查看各 CPU 中断计数 | cat /proc/interrupts | 用计数器增量验证绑定效果 |
| 设置新中断默认掩码 | echo ... > /proc/irq/default_smp_affinity | 默认0xffffffff,作用于未激活/新分配中断 |
排障要点:先确认中断控制器支持亲和性且该中断不是 managed 中断(对应 proc 文件是否有写权限);设置后用ping -f等流量手段制造中断,再对比/proc/interrupts中目标 CPU 与非目标 CPU 的计数增量,即可客观验证绑定是否生效。
参考文档
- SMP IRQ affinity 原文档
- IRQ 基础概念:什么是 IRQ 号
- Affinity managed interrupts 详解
- irq 文档索引
- 相关实现源码:kernel/irq/proc.c、kernel/irq/manage.c
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考