2. Linux中断子系统架构:中断控制器(GIC)、中断描述符、中断请求(IRQ)域

中断子系统是Linux内核响应硬件事件的神经中枢。理解其核心组件——中断控制器(GIC)、中断描述符(irq_desc)以及中断请求域(IRQ Domain)——是进行驱动中断开发与性能调优的基石。本章将深入剖析这三层架构的协作关系与数据结构。

2.1 中断控制器(GIC):硬件中断的交通枢纽

通用中断控制器(Generic Interrupt Controller, GIC)是ARM体系结构下标准的中断管理单元。它负责接收来自外设的物理中断信号,进行优先级仲裁,并最终将中断分发给指定的CPU核心。

GIC主要分为两个逻辑块:分发器(Distributor) 和 CPU接口(CPU Interface)。

组件 功能描述 关键寄存器/特性
分发器 (Distributor) 集中管理所有中断源,控制中断的使能、优先级、触发方式(电平/边沿)和目标CPU。 ICDISER (使能)、ICDICFR (配置)、ICDIPTR (目标CPU)
CPU接口 (CPU Interface) 每个CPU核心私有,负责向该CPU发送中断请求,并处理中断确认(ACK)和结束(EOI)信号。 ICC_IAR (中断确认)、ICC_EOIR (中断结束)、ICC_PMR (优先级掩码)
性能调优要点: 在SMP系统中,通过配置ICDIPTR寄存器将中断亲和性绑定到特定CPU,可以有效避免多核间的缓存抖动,是中断负载均衡的核心手段。
GIC 硬件架构与中断流向 外设 A 外设 B 外设 C GIC 分发器 - 使能/屏蔽 - 优先级仲裁 - 触发方式配置 - 目标CPU选择 CPU0 接口 ICC_IAR/EOIR 优先级掩码 CPU1 接口 ICC_IAR/EOIR 优先级掩码 CPU0 CPU1 中断流向:外设 → 分发器 → CPU接口 → CPU核心 分发器决定“哪个CPU”处理,CPU接口负责“如何”处理

2.2 中断描述符(irq_desc):内核的中断对象模型

在内核中,每个硬件中断源都对应一个 struct irq_desc 结构体。这个结构体是中断子系统的核心对象,包含了中断的所有状态、处理函数指针以及统计信息。

// include/linux/irqdesc.h (简化结构)
struct irq_desc {
    struct irq_common_data  irq_common_data;
    struct irq_data         irq_data;
    unsigned int __percpu   *kstat_irqs;    // 每个CPU的中断统计
    irq_flow_handler_t      handle_irq;     // 高层中断流处理函数
    struct irqaction        *action;        // 中断动作链表(驱动注册的handler)
    unsigned int            status_use_accessors;
    struct proc_dir_entry   *dir;           // /proc/irq/ 目录
    int                     parent_irq;
    struct module           *owner;
    const char              *name;
} ____cacheline_internodealigned_in_smp; // 对齐到缓存行,防止伪共享
关键字段解读:
  • handle_irq:标准流处理函数(如 handle_level_irq, handle_edge_irq),由GIC驱动在初始化时设置。
  • action:链表,挂载了通过 request_irq() 注册的驱动中断处理程序。
  • kstat_irqs:Per-CPU变量,用于记录中断发生次数,是排查中断风暴的第一手数据来源。

2.3 中断请求域(IRQ Domain):硬件中断号到Linux IRQ号的映射

IRQ Domain 是连接硬件中断号(HW IRQ)与Linux虚拟中断号(Linux IRQ)的桥梁。由于设备树或ACPI描述的硬件中断号与内核使用的IRQ号是独立的,Domain层提供了灵活的映射机制。

IRQ Domain 支持三种主要的映射操作:

  1. 线性映射(Linear):预分配一个固定大小的数组,适用于中断源数量固定且较少的场景。
  2. 树形映射(Tree):使用基数树(radix tree)管理,适用于中断源数量大且稀疏的场景。
  3. 不映射(Nomap):硬件中断号直接作为Linux IRQ号,用于简单的嵌入式系统。
映射类型 数据结构 适用场景 查找复杂度
线性映射 数组 (irq_desc[]) 中断源少且连续 (如GPIO控制器) O(1)
树形映射 基数树 (radix_tree) 中断源多且稀疏 (如PCIe MSI/MSI-X) O(log n)
不映射 直接使用 硬件IRQ与Linux IRQ一致 O(1)
调优陷阱: 在中断密集型设备(如万兆网卡)中,如果使用线性映射且中断号范围过大,会导致irq_desc数组占用大量内存。此时应优先考虑树形映射或动态分配。
IRQ Domain 映射流程 HW IRQ (32) IRQ Domain 映射操作 irq_domain_ops (xlate / map / unmap) Linux IRQ (168) 映射过程示例 1. 设备树中描述:interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH> 2. GIC驱动通过 irq_domain_ops.xlate 解析出 HW IRQ = 32 3. irq_domain_ops.map 分配并关联 Linux IRQ = 168

2.4 三者协作:从硬件中断到驱动处理

当外设触发中断时,完整的软件处理路径如下:

  1. GIC分发器 接收中断信号,根据配置选择目标CPU。
  2. 目标CPU的 GIC CPU接口 触发IRQ异常,CPU跳转到异常向量表。
  3. 内核汇编入口保存现场,调用 handle_arch_irq(),该函数读取 ICC_IAR 获取硬件中断号。
  4. 通过 IRQ Domain 将硬件中断号转换为 Linux IRQ号。
  5. 根据Linux IRQ号索引 irq_desc,调用其 handle_irq 流处理函数。
  6. 流处理函数调用 action 链表中驱动注册的 handler。
  7. 驱动处理完成后,写 ICC_EOIR 通知GIC中断处理结束。
性能关键路径: 步骤4的IRQ Domain查找和步骤5的irq_desc访问是中断响应延迟的主要软件开销。使用线性映射和确保irq_desc结构体缓存行对齐(____cacheline_internodealigned_in_smp)是优化这两步的常用手段。