3. 中断描述符与 irq_chip:struct irq_desc 结构体分析、irq_chip 硬件接口层、中断控制器驱动模型
中断子系统在内核中的核心数据结构是 struct irq_desc(中断描述符),它串联起从硬件中断触发到软件处理函数执行的完整链路。而 struct irq_chip 则是内核与具体中断控制器硬件之间的抽象接口层,屏蔽了不同厂商(如 Intel APIC、ARM GIC、RISC-V PLIC)的寄存器差异。本章将深入分析这两个核心结构体,并梳理中断控制器驱动的标准模型。
3.1 struct irq_desc:中断描述符的完整画像
每个 IRQ 号(无论是全局的 Linux IRQ 号还是硬件 IRQ 号)在内核中都对应一个 irq_desc 实例。该结构体包含了该中断线的全部状态、处理函数链表、亲和性设置、统计信息以及底层硬件操作指针。
irq_desc 是中断处理流程的“控制块”,所有与中断相关的动态信息都存储在此,避免了全局散列表的查找开销。
3.1.1 核心字段解析
| 字段名 | 类型 | 作用与说明 |
|---|---|---|
irq_data |
struct irq_data |
包含 IRQ 号、irq_chip 指针、irq_domain 映射数据、硬件 IRQ 号(hwirq)以及中断控制器私有数据(chip_data)。 |
action |
struct irqaction * |
指向中断处理动作链表的头指针。一个中断线可以共享多个处理函数(如 PCI 共享中断)。 |
status_use_accessors |
unsigned int |
中断状态标志位,如 IRQ_DISABLED、IRQ_INPROGRESS、IRQ_MASKED 等。内核提供了 irqd_irq_disabled() 等访问器。 |
depth |
unsigned int |
中断禁用嵌套深度。每次调用 disable_irq() 递增,enable_irq() 递减。当 depth 为 0 时中断才真正被使能。 |
affinity_hint |
cpumask_var_t |
内核或驱动建议的 CPU 亲和性掩码,用于指导中断负载均衡。 |
pending_mask |
cpumask_var_t |
在中断迁移过程中,记录尚未被目标 CPU 处理的中断请求。 |
kstat_irqs |
struct irqstat * |
每个 CPU 的中断统计计数,用于 /proc/interrupts 展示和性能分析。 |
3.1.2 irq_desc 与中断处理流程
当中断控制器向 CPU 发送中断信号时,CPU 跳转到入口函数(如 handle_irq() 或 generic_handle_irq()),通过 IRQ 号索引到对应的 irq_desc,然后调用 desc->handle_irq()(高层流控处理函数)。该函数根据中断类型(边沿触发、电平触发)执行不同的流控逻辑,最终遍历 desc->action 链表,调用注册的 irq_handler_t。
irq_desc 数组在系统启动时分配,大小由 NR_IRQS 决定。在支持 SPARSE_IRQ 的系统中,irq_desc 按需分配,避免了大数组的内存浪费,但也引入了 radix tree 查找的微小开销。
3.1.3 irq_desc 关系内联 SVG
3.2 irq_chip:硬件接口层的抽象
struct irq_chip 是内核与中断控制器硬件交互的契约。它定义了一组回调函数指针,用于操作中断控制器的寄存器,实现中断的使能、屏蔽、确认、结束、亲和性设置等底层操作。
3.2.1 核心回调函数
| 回调函数 | 原型 | 典型操作 |
|---|---|---|
irq_mask |
void (*)(struct irq_data *) |
屏蔽中断源(写中断屏蔽寄存器),禁止该中断线传递信号到 CPU。 |
irq_unmask |
void (*)(struct irq_data *) |
取消屏蔽,允许中断信号通过。 |
irq_ack |
void (*)(struct irq_data *) |
确认中断(ACK),通常用于边沿触发中断,清除中断挂起位。 |
irq_eoi |
void (*)(struct irq_data *) |
中断结束(End Of Interrupt),用于电平触发中断或需要显式 EOI 的控制器(如 GIC)。 |
irq_set_affinity |
int (*)(struct irq_data *, const struct cpumask *, bool) |
设置中断亲和性,将中断路由到指定 CPU 或 CPU 组。这是中断负载均衡的核心硬件操作。 |
irq_set_type |
int (*)(struct irq_data *, unsigned int) |
设置中断触发类型(边沿上升、边沿下降、电平高、电平低)。 |
irq_chip_mask_parent)。但 irq_mask 和 irq_unmask 通常是必须的,否则中断无法正常开关。
3.2.2 irq_chip 与 irq_desc 的绑定
在中断控制器驱动初始化时,会分配并填充一个 irq_chip 实例,然后通过 irq_set_chip() 或 irq_domain 映射机制将其绑定到对应的 irq_desc 的 irq_data.chip 字段。当中断流控处理函数需要操作硬件时,通过 irq_desc->irq_data.chip 调用对应的回调。
3.2.3 irq_chip 回调调用流程 SVG
3.3 中断控制器驱动模型
Linux 内核的中断控制器驱动遵循一套标准化的模型,主要围绕 irq_domain(中断域)和 irq_chip 展开。该模型将硬件中断号(hwirq)映射到 Linux IRQ 号,并关联对应的 irq_chip 回调。
3.3.1 驱动模型层次结构
- 设备树/ACPI 解析阶段: 内核解析硬件描述信息,获取中断控制器的寄存器基地址、中断输出线数量、级联关系等。
- irq_domain 创建: 驱动调用
irq_domain_add_linear()、irq_domain_add_tree()或irq_domain_add_simple()创建映射域。线性域适用于 hwirq 连续且数量固定的场景;树形域适用于稀疏 hwirq 场景。 - irq_chip 初始化: 驱动填充
struct irq_chip实例,设置name、irq_mask、irq_unmask、irq_set_affinity等回调。 - irq_desc 分配与绑定: 当设备请求中断时,
irq_domain分配一个空闲的 Linux IRQ 号,创建irq_desc,并将irq_chip和 hwirq 信息写入irq_data。 - 中断处理注册: 设备驱动调用
request_irq()或request_threaded_irq(),将处理函数挂入irq_desc->action链表。
3.3.2 常见中断控制器对比
| 控制器 | 架构 | irq_domain 类型 | 亲和性设置方式 | 特殊回调 |
|---|---|---|---|---|
| x86 APIC | x86 | 线性域 (linear) | IOAPIC 重定向表项 | irq_set_affinity 写 IOREDTBL |
| ARM GICv2/v3 | ARM/ARM64 | 线性域 (linear) | GICD_ITARGETSR / GICR_IPRIORITYR | irq_eoi 写 GICC_EOIR |
| RISC-V PLIC | RISC-V | 树形域 (tree) | PLIC 优先级阈值(全局) | irq_eoi 写 PLIC_CLAIM |
| LoongArch LIOINTC | LoongArch | 线性域 (linear) | 路由表项配置 | irq_mask/irq_unmask 写 EN/INT 寄存器 |
3.3.3 中断控制器驱动模型架构 SVG
3.4 关键代码片段:irq_chip 注册示例
以下是一个简化的中断控制器驱动初始化代码,展示了 irq_chip 的填充与 irq_domain 的创建过程。
/* 示例:模拟中断控制器的 irq_chip 定义 */
static struct irq_chip my_controller_chip = {
.name = "my_controller",
.irq_mask = my_controller_mask,
.irq_unmask = my_controller_unmask,
.irq_ack = my_controller_ack,
.irq_set_affinity = my_controller_set_affinity,
.irq_set_type = my_controller_set_type,
};
/* irq_domain 映射回调:将 hwirq 映射到 irq_desc */
static int my_controller_domain_map(struct irq_domain *d,
unsigned int virq, irq_hw_number_t hw)
{
/* 将 irq_chip 绑定到 virq 对应的 irq_desc */
irq_set_chip_and_handler(virq, &my_controller_chip,
handle_level_irq);
/* 设置中断触发类型为高电平有效 */
irq_set_irq_type(virq, IRQ_TYPE_LEVEL_HIGH);
/* 设置芯片私有数据 */
irq_set_chip_data(virq, d->host_data);
return 0;
}
/* 驱动初始化函数 */
static int __init my_controller_init(void)
{
struct irq_domain *domain;
/* 创建线性 irq_domain,支持 32 个中断源 */
domain = irq_domain_add_linear(NULL, 32,
&my_controller_domain_ops, NULL);
if (!domain)
return -ENOMEM;
pr_info("my_controller: irq_domain created with 32 interrupts\n");
return 0;
}
irq_set_chip_and_handler() 同时绑定了 irq_chip 和流控处理函数(如 handle_level_irq 或 handle_edge_irq)。流控函数的选择取决于中断触发类型,错误的选择会导致中断丢失或死锁。
3.5 中断描述符与 irq_chip 的协同工作
理解 irq_desc 和 irq_chip 的关系是进行中断负载均衡优化的基础。负载均衡的核心操作——设置亲和性——最终通过 irq_chip 的 irq_set_affinity 回调写入硬件寄存器。而 irq_desc 中的 affinity_hint 和 pending_mask 则为软件层面的均衡策略提供了决策依据。
- irq_desc 是“信息中心”: 存储了中断的当前状态、处理函数、统计数据和软件亲和性掩码。
- irq_chip 是“执行器”: 将软件决策(如亲和性变更、屏蔽/使能)翻译为硬件寄存器操作。
- irq_domain 是“翻译官”: 将硬件中断号(hwirq)转换为内核统一的 Linux IRQ 号,并完成
irq_desc的初始化绑定。
/proc/interrupts 确认中断分布,然后通过 /proc/irq/<IRQ>/smp_affinity 查看当前亲和性设置。如果发现中断集中在某个 CPU 上,需要回溯到 irq_chip 的 irq_set_affinity 实现,确认硬件是否支持动态路由,以及驱动是否正确实现了该回调。