3. 中断描述符 (irq_desc):数据结构、中断状态、中断处理函数链

中断描述符 irq_desc 是 Linux 内核中断子系统的核心数据结构,每个硬件中断源(IRQ 号)对应一个 irq_desc 实例。它串联了中断控制器、中断状态、处理函数链以及线程化处理等关键组件,是理解中断处理流程的入口。

3.1 irq_desc 数据结构全景

irq_desc 定义在 include/linux/irqdesc.h 中,其核心字段如下表所示:

字段 类型 作用
irq_data struct irq_data 封装中断控制器 (chip)、硬件 IRQ 号、中断域 (domain) 等底层信息
status_use_accessors unsigned int 中断状态位图,通过 irqd_* 系列宏访问
action struct irqaction * 指向中断处理函数链表的头节点
depth unsigned int 中断禁用深度计数,0 表示启用
istate unsigned long 中断内部状态(IRQS_* 标志)
wait_for_threads wait_queue_head_t 等待所有线程化处理完成
kstat_irqs unsigned int * 每个 CPU 的中断统计计数
关键设计: irq_desc 通过 irq_data 解耦了中断控制器硬件细节,使得同一套中断处理框架可以适配 GIC、IO-APIC、Xive 等不同中断控制器。

3.2 中断状态机

每个 irq_desc 维护一组状态标志,控制中断的激活、屏蔽、等待处理等生命周期。核心状态定义在 include/linux/irq.h 中:

  • IRQS_AUTODETECT — 中断号自动探测中
  • IRQS_SPURIOUS_DISABLED — 因伪中断被自动禁用
  • IRQS_POLL_INPROGRESS — 轮询检测进行中
  • IRQS_ONESHOT — 线程化中断的一次性触发模式
  • IRQS_WAITING — 等待中断确认
  • IRQS_SUSPENDED — 系统挂起中

状态转换的核心路径如下:

  1. 中断触发 → handle_irq_event_percpu() 设置 IRQS_INPROGRESS
  2. 处理函数链执行 → 清除 IRQS_INPROGRESS
  3. 若检测到伪中断 → 设置 IRQS_SPURIOUS_DISABLED 并屏蔽硬件中断
  4. 线程化中断处理完成 → 清除 IRQS_ONESHOT,重新使能硬件中断
实践提示: 通过 /proc/interrupts 可以查看每个中断的触发次数和 CPU 亲和性,结合 irq_desc 的 kstat_irqs 字段,是排查中断风暴的第一手工具。

3.3 中断处理函数链 (action 链表)

irq_desc->action 指向一个 struct irqaction 的单向链表。每个 irqaction 代表一个注册的中断处理实体,支持多个设备共享同一 IRQ 号。

3.3.1 irqaction 结构

字段 说明
handler 中断处理函数指针 (irq_handler_t)
thread_fn 线程化处理函数(若为 NULL 则非线程化)
thread 内核线程 task_struct 指针
name 设备名称,用于 /proc/interrupts 显示
dev_id 设备标识,用于共享中断时区分设备
flags 中断标志(IRQF_* 系列)
next 指向下一个 irqaction,形成链表

3.3.2 共享中断的遍历逻辑

当硬件中断触发时,内核遍历 action 链表,依次调用每个 irqaction->handler。处理函数必须返回 IRQ_HANDLED 或 IRQ_NONE:

  • IRQ_HANDLED — 该设备确认处理了中断
  • IRQ_NONE — 该设备不是中断源,继续遍历

若所有处理函数都返回 IRQ_NONE,内核判定为伪中断,触发 IRQS_SPURIOUS_DISABLED 逻辑。

性能陷阱: 共享中断链上的每个处理函数都会在中断上下文中执行。若某个设备驱动处理时间过长,会阻塞其他共享设备的响应。建议对延迟敏感的设备使用专用 IRQ 或线程化中断。

3.4 irq_desc 与中断控制器的交互

下图展示了 irq_desc 如何通过 irq_data 桥接中断控制器硬件:

irq_desc 与中断控制器交互架构 irq_desc irq_data action 链表 中断控制器 (chip) 硬件中断线 irqaction 1 irqaction 2 irqaction N irq_data 封装 chip 操作函数集,action 链表保存所有注册的处理函数

3.5 中断处理函数链的执行流程

当中断发生时,内核通过 handle_irq_desc() 进入通用中断处理层,执行以下步骤:

  1. 获取 irq_desc — 根据 IRQ 号从全局数组 irq_desc[] 中索引
  2. 状态检查 — 确认中断未屏蔽、未挂起、未处于自动探测状态
  3. 调用 chip->irq_ack() — 应答中断控制器,防止重复触发
  4. 遍历 action 链表 — 依次调用每个 irqaction->handler
  5. 统计与清理 — 更新 kstat_irqs,调用 chip->irq_eoi() 结束中断
内核 5.10+ 优化: 引入 handle_irq_event_percpu() 批量处理,减少锁竞争。对于纯轮询模式(如 NAPI),中断处理函数仅触发软中断,实际数据包处理在 net_rx_action 中完成。

3.6 线程化中断与 action 链表的协同

当 irqaction->thread_fn 不为 NULL 时,中断处理分为两部分:

  • 上半部 (handler) — 在中断上下文中快速执行,通常仅做硬件应答并唤醒线程
  • 下半部 (thread_fn) — 在进程上下文中执行,可睡眠、可持有锁

线程化中断通过 irq_desc->wait_for_threads 等待队列同步,确保所有线程化处理完成后才重新使能硬件中断。

特性 传统中断 线程化中断
执行上下文 中断上下文(不可睡眠) 进程上下文(可睡眠)
锁限制 只能使用 spinlock 可使用 mutex
延迟 低(直接执行) 较高(依赖调度)
适用场景 简单、快速处理 复杂、慢速设备驱动
调试技巧: 使用 cat /proc/irq/<IRQ_NUM>/smp_affinity 查看中断 CPU 亲和性;使用 perf top -G 观察中断处理函数的热点分布。

通过 irq_desc 的完整设计,Linux 内核实现了中断处理的高度模块化:中断控制器驱动只需实现 irq_chip 操作集,设备驱动只需注册 irqaction,而通用中断层负责状态管理、函数链遍历和统计,为 NAPI 轮询等高级机制提供了稳定的基础。