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 — 系统挂起中
状态转换的核心路径如下:
- 中断触发 →
handle_irq_event_percpu()设置 IRQS_INPROGRESS - 处理函数链执行 → 清除 IRQS_INPROGRESS
- 若检测到伪中断 → 设置 IRQS_SPURIOUS_DISABLED 并屏蔽硬件中断
- 线程化中断处理完成 → 清除 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 逻辑。
3.4 irq_desc 与中断控制器的交互
下图展示了 irq_desc 如何通过 irq_data 桥接中断控制器硬件:
3.5 中断处理函数链的执行流程
当中断发生时,内核通过 handle_irq_desc() 进入通用中断处理层,执行以下步骤:
- 获取 irq_desc — 根据 IRQ 号从全局数组
irq_desc[]中索引 - 状态检查 — 确认中断未屏蔽、未挂起、未处于自动探测状态
- 调用 chip->irq_ack() — 应答中断控制器,防止重复触发
- 遍历 action 链表 — 依次调用每个
irqaction->handler - 统计与清理 — 更新
kstat_irqs,调用chip->irq_eoi()结束中断
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 轮询等高级机制提供了稳定的基础。