/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 到内核实际更新中断分发目标的全流程:

中断亲和性设置流程 中断亲和性设置流程(smp_affinity_list 写入路径) 用户态:echo "0,2-4" > /proc/irq/130/smp_affinity_list VFS 层:proc 文件系统写操作 内核:irq_affinity_list_proc_write() 解析 CPU 列表 → 生成 cpumask 位图 irq_set_affinity() → 更新 irq_desc 亲和性 中断控制器重编程 → 中断分发到指定 CPU 关键说明: • smp_affinity_list 更易读 • 两个文件共享同一底层位图 • 写入后立即生效,无需重启 内核版本差异: • Linux 2.6.12+ 支持 • 4.8+ 支持范围表示法 • 5.0+ 增强 NUMA 感知

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 集合
生产环境警告: 在关键业务系统上修改中断亲和性前,务必先在测试环境验证。错误的亲和性配置可能导致中断集中在某个 CPU 上,造成该 CPU 负载过高,而其他 CPU 空闲,反而降低整体性能。建议逐步调整并监控 CPU 使用率和中断分布。

4.8 本章小结

/proc/irq/ 目录是 Linux 中断亲和性管理的核心接口。通过 smp_affinity 和 smp_affinity_list 两个文件,系统管理员可以精确控制每个中断的 CPU 分发目标。理解这两个文件的格式、同步关系以及内核实现路径,是进行网络中断性能优化的基础。