4. /proc/irq/目录结构
在 Linux 内核中,/proc/irq/ 是用户态查看和调整中断行为的主入口。每个已注册的中断号都会在该目录下对应一个子目录,目录名即中断号(IRQ number)。通过读取和写入这些文件,可以精确控制中断的 CPU 亲和性、中断触发类型、中断统计信息等。
/proc/irq/ 是内核中断子系统的 procfs 接口,它将每个中断源暴露为一个目录,目录内包含多个属性文件。其中 smp_affinity 和 smp_affinity_list 是控制中断分发到哪些 CPU 核心的关键文件。
4.1 目录结构总览
以下是一个典型的多核服务器上 /proc/irq/ 的部分目录结构示例:
$ ls -la /proc/irq/
dr-xr-xr-x ... 0/
dr-xr-xr-x ... 1/
dr-xr-xr-x ... 8/
dr-xr-xr-x ... 16/
dr-xr-xr-x ... 24/
dr-xr-xr-x ... 32/
dr-xr-xr-x ... 40/
dr-xr-xr-x ... 48/
dr-xr-xr-x ... 56/
dr-xr-xr-x ... 64/
dr-xr-xr-x ... 72/
dr-xr-xr-x ... 80/
dr-xr-xr-x ... 88/
dr-xr-xr-x ... 96/
dr-xr-xr-x ... 104/
dr-xr-xr-x ... 112/
dr-xr-xr-x ... 120/
dr-xr-xr-x ... 128/
dr-xr-xr-x ... 130/
dr-xr-xr-x ... 131/
dr-xr-xr-x ... 132/
dr-xr-xr-x ... 133/
dr-xr-xr-x ... 134/
dr-xr-xr-x ... 135/
-rw-r--r-- ... default_smp_affinity
每个中断号目录下通常包含以下文件:
| 文件/目录名 | 类型 | 描述 |
|---|---|---|
smp_affinity |
文件 | 中断亲和性掩码,十六进制格式,位图表示允许处理该中断的 CPU 集合 |
smp_affinity_list |
文件 | 中断亲和性列表,十进制 CPU 编号列表格式,更易读 |
node |
文件 | 该中断关联的 NUMA 节点编号(如果支持) |
type |
文件 | 中断类型描述,如 "PCI-MSI"、"IO-APIC"、"IR-IO-APIC" 等 |
actions |
目录 | 包含注册到该中断号的所有设备驱动名称及处理函数信息 |
spurious |
文件 | 记录该中断的虚假中断计数(用于调试) |
per_cpu |
目录 | 每个 CPU 核心上该中断的统计信息(中断次数、时间戳等) |
msi_desc |
目录 | MSI/MSI-X 中断的描述符信息(仅对 MSI 中断存在) |
cat /proc/irq/<IRQ>/type 确认中断类型。PCI-MSI 中断通常支持动态亲和性调整,而某些旧式 IO-APIC 中断可能受限。
4.2 smp_affinity 文件详解
smp_affinity 文件以十六进制位图(bitmask)的形式存储中断的 CPU 亲和性。每一位代表一个 CPU 核心,位值为 1 表示该 CPU 允许处理此中断,位值为 0 表示不允许。
位图与 CPU 编号的对应关系:
- 最低位(bit 0)对应 CPU 0
- bit 1 对应 CPU 1
- bit 2 对应 CPU 2
- 以此类推
示例: 假设系统有 8 个 CPU(0-7),要将中断绑定到 CPU 2 和 CPU 3:
# 二进制位图:00001100(bit 2 和 bit 3 为 1)
# 十六进制表示:0x0c
# 设置亲和性
$ echo 0c > /proc/irq/130/smp_affinity
# 验证
$ cat /proc/irq/130/smp_affinity
00000000,0000000c
- 写入
smp_affinity时,十六进制字符串不能带 "0x" 前缀,直接写数字即可。 - 对于超过 32 个 CPU 的系统,
smp_affinity会显示为逗号分隔的多组 32 位十六进制数(如00000000,0000000c),写入时也需要对应格式。 - 写入的位图必须至少包含一个允许的 CPU,否则写入会被拒绝。
4.3 smp_affinity_list 文件详解
smp_affinity_list 是 smp_affinity 的“人类可读”版本。它使用十进制 CPU 编号列表,支持范围表示法(如 0-3)和逗号分隔。
格式规则:
- 单个 CPU:
2 - 多个 CPU 逗号分隔:
2,3 - 连续范围:
2-3 - 混合:
0,2-4,7
示例: 将中断 130 绑定到 CPU 0、2、3、4、7:
# 使用 smp_affinity_list 设置
$ echo 0,2-4,7 > /proc/irq/130/smp_affinity_list
# 验证(两个文件会同步更新)
$ cat /proc/irq/130/smp_affinity_list
0,2-4,7
$ cat /proc/irq/130/smp_affinity
00000000,10000101
smp_affinity_list 进行设置,因为它更直观、不易出错。但在需要与位运算或内核代码交互时,smp_affinity 的十六进制格式更直接。
4.4 两个文件的同步关系与内核实现
smp_affinity 和 smp_affinity_list 是同一底层数据结构的两种视图。内核在 kernel/irq/proc.c 中实现了这两个文件的读写操作,它们共享同一个 irq_desc->irq_common_data.affinity 位图。
同步机制:
- 写入
smp_affinity时,内核解析十六进制字符串,更新位图,同时smp_affinity_list在下一次读取时会自动反映新值。 - 写入
smp_affinity_list时,内核解析 CPU 列表,更新位图,smp_affinity也会同步更新。 - 两个文件始终反映相同的亲和性配置,不存在不一致的情况。
内核关键代码路径:
// kernel/irq/proc.c
static int irq_affinity_proc_show(struct seq_file *m, void *v)
{
// 读取 irq_desc->irq_common_data.affinity
// 输出为十六进制位图格式
}
static int irq_affinity_list_proc_show(struct seq_file *m, void *v)
{
// 读取相同的 affinity 位图
// 输出为 CPU 列表格式
}
static ssize_t irq_affinity_proc_write(struct file *file,
const char __user *buffer, size_t count, loff_t *pos)
{
// 解析十六进制输入,调用 irq_set_affinity()
}
static ssize_t irq_affinity_list_proc_write(struct file *file,
const char __user *buffer, size_t count, loff_t *pos)
{
// 解析 CPU 列表输入,调用 irq_set_affinity()
}
4.5 中断亲和性设置流程(SVG 图)
下图展示了从用户态写入 smp_affinity_list 到内核实际更新中断分发目标的全流程:
4.6 实际案例:查看和修改网卡中断亲和性
以下是一个完整的实战示例,展示如何查看和修改特定网卡(如 eth0)的中断亲和性:
步骤 1:找到网卡对应的中断号
# 查看 /proc/interrupts,找到 eth0 对应的中断号
$ cat /proc/interrupts | grep eth0
130: 12345 67890 12345 67890 PCI-MSI eth0-rx-0
131: 23456 78901 23456 78901 PCI-MSI eth0-tx-0
步骤 2:查看当前亲和性配置
# 查看中断 130 的当前亲和性
$ cat /proc/irq/130/smp_affinity
00000000,0000000f
$ cat /proc/irq/130/smp_affinity_list
0-3
步骤 3:修改亲和性(将中断 130 绑定到 CPU 2 和 CPU 3)
# 方法一:使用 smp_affinity_list(推荐)
$ echo 2,3 > /proc/irq/130/smp_affinity_list
# 方法二:使用 smp_affinity(十六进制)
# CPU 2 和 CPU 3 对应 bit 2 和 bit 3,位图 = 00001100 = 0x0c
$ echo 0c > /proc/irq/130/smp_affinity
# 验证修改结果
$ cat /proc/irq/130/smp_affinity_list
2,3
$ cat /proc/irq/130/smp_affinity
00000000,0000000c
步骤 4:验证中断分布变化
# 观察中断计数变化(需要产生网络流量)
$ watch -n 1 'cat /proc/interrupts | grep "130:"'
# 正常情况下,中断只会出现在 CPU 2 和 CPU 3 上
ixgbe、mlx5),每个队列通常有独立的中断号。建议将每个队列的中断绑定到不同的 CPU 核心,实现 RSS(Receive Side Scaling)负载均衡。例如,4 队列网卡可以分别绑定到 CPU 0-3。
4.7 常见问题与避坑指南
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 写入 smp_affinity 返回 "Invalid argument" | 十六进制格式错误,或位图全零 | 确保至少包含一个 1 位,且格式正确(无 0x 前缀) |
| 写入 smp_affinity_list 返回 "Invalid argument" | CPU 编号超出系统范围,或格式错误 | 检查 CPU 编号范围(0 到 n-1),使用正确格式如 "0,2-4" |
| 修改后中断分布未变化 | 中断控制器不支持动态重编程,或驱动未实现 set_affinity 回调 | 检查中断类型,部分旧式 IO-APIC 中断可能不支持 |
| 系统重启后亲和性丢失 | /proc/irq/ 中的设置是临时的,重启后恢复默认 | 将设置写入启动脚本(如 /etc/rc.local)或使用 irqbalance 配置 |
| smp_affinity 显示为全 f | 默认值,表示所有 CPU 都允许处理该中断 | 根据需求修改为特定 CPU 集合 |
4.8 本章小结
/proc/irq/ 目录是 Linux 中断亲和性管理的核心接口。通过 smp_affinity 和 smp_affinity_list 两个文件,系统管理员可以精确控制每个中断的 CPU 分发目标。理解这两个文件的格式、同步关系以及内核实现路径,是进行网络中断性能优化的基础。