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

struct irq_desc 核心结构关系图 struct irq_desc struct irq_data irq (Linux IRQ号) hwirq (硬件IRQ号) chip (irq_chip指针) chip_data (控制器私有数据) struct irqaction * handler (处理函数) dev_id (设备标识) irq (关联IRQ号) next (链表下一节点) 状态与亲和性 status_use_accessors depth (嵌套深度) affinity_hint pending_mask handle_irq (流控函数) kstat_irqs (per-CPU统计) 每个 irq_desc 独立管理一条中断线的完整生命周期:从硬件映射、流控处理到软件回调与统计。

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

irq_chip 回调函数调用流程 硬件中断触发 handle_irq (流控函数) 触发类型? 边沿 chip->irq_ack() 电平 chip->irq_mask() 执行 action->handler() 边沿触发:先 ACK 清除挂起位;电平触发:先 MASK 防止重复触发,处理完再 UNMASK。

3.3 中断控制器驱动模型

Linux 内核的中断控制器驱动遵循一套标准化的模型,主要围绕 irq_domain(中断域)和 irq_chip 展开。该模型将硬件中断号(hwirq)映射到 Linux IRQ 号,并关联对应的 irq_chip 回调。

3.3.1 驱动模型层次结构

  1. 设备树/ACPI 解析阶段: 内核解析硬件描述信息,获取中断控制器的寄存器基地址、中断输出线数量、级联关系等。
  2. irq_domain 创建: 驱动调用 irq_domain_add_linear()、irq_domain_add_tree() 或 irq_domain_add_simple() 创建映射域。线性域适用于 hwirq 连续且数量固定的场景;树形域适用于稀疏 hwirq 场景。
  3. irq_chip 初始化: 驱动填充 struct irq_chip 实例,设置 name、irq_mask、irq_unmask、irq_set_affinity 等回调。
  4. irq_desc 分配与绑定: 当设备请求中断时,irq_domain 分配一个空闲的 Linux IRQ 号,创建 irq_desc,并将 irq_chip 和 hwirq 信息写入 irq_data。
  5. 中断处理注册: 设备驱动调用 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

中断控制器驱动模型架构 硬件层:中断控制器寄存器(GICD / IOAPIC / PLIC) MMIO 地址空间:屏蔽寄存器、挂起寄存器、亲和性路由表、EOI 寄存器 struct irq_chip 回调函数层 mask / unmask / ack / eoi / set_affinity / set_type struct irq_domain 映射层 hwirq → Linux IRQ 号映射 / 线性域 / 树形域 struct irq_desc 中断描述符层 状态管理 / 亲和性掩码 / 处理函数链表 / 统计计数 设备驱动层:request_irq / request_threaded_irq 注册中断处理函数 / 管理中断线程化 / 处理共享中断 驱动模型自底向上:硬件寄存器 → irq_chip 回调 → irq_domain 映射 → irq_desc 管理 → 设备驱动处理。 每一层都通过标准接口解耦,使得更换中断控制器硬件时无需修改上层代码。

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 实现,确认硬件是否支持动态路由,以及驱动是否正确实现了该回调。