3. Linux 中断子系统架构:分层、irq_chip、irq_domain 与中断描述符

中断子系统是 Linux 内核中连接硬件与驱动软件的核心枢纽。为了在 x86、ARM、RISC-V 等不同架构上实现统一的驱动接口,内核将中断处理抽象为分层架构,并通过 irq_chip、irq_domain 和 中断描述符 三个关键概念完成硬件中断号到 Linux IRQ 号的映射与管理。

核心认知: 驱动开发者通常只与“Linux IRQ 号”和“中断处理函数”打交道,而中断子系统底层负责将硬件管脚上的电气信号,安全、高效地转化为内核可调度的执行流。

3.1 中断子系统分层架构

Linux 中断子系统从上到下分为三层,每一层职责清晰,通过标准接口解耦:

层次 核心组件 主要职责 驱动开发者关注点
通用中断层 (Generic IRQ) irq_desc、request_irq()、handle_irq() 维护中断描述符、管理中断流控类型 (边缘/电平)、调度执行 handler 调用 request_irq() / devm_request_irq() 注册 handler
中断芯片层 (irq_chip) struct irq_chip 封装硬件中断控制器 (GIC、IOAPIC、GPIO) 的底层操作 实现 irq_chip 回调 (mask/unmask/ack)
中断域层 (irq_domain) struct irq_domain 将硬件中断源 (hwirq) 映射到 Linux IRQ 号 在设备树或 ACPI 中描述中断连接,注册 irq_domain
分层价值: 更换 SoC 或中断控制器时,只需替换 irq_chip 和 irq_domain 的实现,上层的通用中断层和驱动代码无需修改。
Linux 中断子系统三层架构 Linux 中断子系统分层架构 通用中断层 (Generic IRQ Layer) irq_desc 管理 | 流控处理 (handle_edge_irq / handle_level_irq) | request_irq() / free_irq() 中断芯片层 (irq_chip Layer) irq_mask / irq_unmask / irq_ack / irq_set_type | 硬件寄存器操作 中断域层 (irq_domain Layer) hwirq → Linux IRQ 映射 | 设备树/ACPI 解析 | 线性/树形映射 驱动开发者主要接触此层

3.2 irq_chip:硬件中断控制器的软件抽象

struct irq_chip 是内核为中断控制器定义的通用接口结构体。每个中断控制器 (如 GICv2、GICv3、IOAPIC) 都需要实例化该结构体,并实现必要的回调函数。

// include/linux/irq.h
struct irq_chip {
    const char      *name;           // 芯片名称,用于 /proc/interrupts
    unsigned int    (*irq_mask)(struct irq_data *data);      // 屏蔽中断
    unsigned int    (*irq_unmask)(struct irq_data *data);    // 取消屏蔽
    void            (*irq_ack)(struct irq_data *data);       // 应答中断 (边缘触发)
    void            (*irq_eoi)(struct irq_data *data);       // 中断结束 (电平触发)
    int             (*irq_set_type)(struct irq_data *data, unsigned int flow_type);
    int             (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);
    // ... 其他可选回调
};
回调函数 触发场景 典型实现
irq_mask 中断被禁用或 CPU 进入休眠 写中断控制器屏蔽寄存器
irq_unmask 中断被重新使能 写中断控制器使能寄存器
irq_ack 边缘触发中断到达,CPU 自动调用 写中断确认寄存器,清除挂起位
irq_eoi 电平触发中断处理完成后 写中断结束寄存器,允许新中断
irq_set_type 驱动调用 irq_set_irq_type() 配置触发方式 (上升沿/高电平)
常见陷阱: 边缘触发中断必须实现 irq_ack,电平触发中断必须实现 irq_eoi。混淆两者会导致中断丢失或死锁。

3.3 irq_domain:硬件中断号到 Linux IRQ 号的桥梁

在设备树或 ACPI 时代,硬件中断源 (hwirq) 是动态的,无法预分配 Linux IRQ 号。irq_domain 负责在运行时建立 hwirq → Linux IRQ 的映射关系。

三种常见的映射方式:

  • 线性映射 (Linear):适用于 hwirq 编号连续且数量固定的控制器 (如 GIC-400)。查找速度快,O(1) 复杂度。
  • 树形映射 (Tree):适用于 hwirq 稀疏或不连续的场景 (如 GPIO 中断控制器)。使用基数树存储,节省内存。
  • 不映射 (No Map):由驱动手动指定 IRQ 号,仅用于 x86 等固定中断号的 legacy 系统。
// 设备树中描述中断连接示例 (ARM GIC)
gic: interrupt-controller@f0001000 {
    compatible = "arm,gic-400";
    interrupt-controller;
    #interrupt-cells = <3>;  // <中断类型 中断号 触发方式>
};

uart0: serial@f0002000 {
    compatible = "arm,pl011";
    interrupts = <0 33 4>;   // SPI, hwirq=33, 高电平触发
    interrupt-parent = <&gic>;
};

内核解析设备树时,irq_domain 会为 uart0 分配一个 Linux IRQ 号 (如 45),驱动只需使用该 IRQ 号调用 request_irq()。

irq_domain 映射流程 irq_domain 映射流程:hwirq → Linux IRQ 硬件中断源 hwirq = 33 irq_domain 线性/树形查找 Linux IRQ 号 irq = 45 映射过程详解 1. 驱动调用 platform_get_irq(pdev, 0) 或 of_irq_get(np, 0) 2. 内核通过 device_node 找到 interrupt-parent (gic irq_domain) 3. irq_domain 根据 hwirq=33 分配/查找 Linux IRQ=45,并建立映射 4. 驱动使用 irq=45 调用 request_irq(45, handler, flags, name, dev)

3.4 中断描述符 (irq_desc)

每个 Linux IRQ 号对应一个 struct irq_desc,它是中断子系统的核心数据结构,包含了该中断的所有状态信息。

// kernel/irq/internals.h (简化)
struct irq_desc {
    struct irq_common_data  irq_common_data;
    struct irq_data         irq_data;          // 指向 irq_chip 和 irq_domain
    unsigned int            __percpu   *kstat_irqs;  // 统计信息
    irq_flow_handler_t      handle_irq;        // 流控处理函数
    struct irqaction        *action;           // 链表,挂载所有 handler
    unsigned int            status_use_accessors;
    struct proc_dir_entry   *dir;              // /proc/irq/ 目录
    const char              *name;             // 中断名称
};
字段 作用 驱动关联
handle_irq 中断流控处理函数 (边缘/电平/简单) 由 irq_chip 和触发类型决定
action 指向 irqaction 链表的头节点 request_irq() 创建的 handler 挂载在此
irq_data 包含 irq_chip 和 irq_domain 指针 驱动通过 irq_get_irq_data() 访问
kstat_irqs 每个 CPU 的中断计数 /proc/interrupts 显示的数据来源
调试技巧: 查看 /proc/interrupts 可以确认中断是否被正确注册、触发次数以及 CPU 亲和性。如果某个中断计数不增长,说明硬件未触发或 irq_domain 映射错误。
irq_desc 数据结构关系 irq_desc 数据结构关系图 struct irq_desc handle_irq | action | irq_data | kstat_irqs struct irq_data irq_chip | irq_domain | hwirq struct irqaction (链表) handler | dev_id | irq | flags /proc/irq/N/ smp_affinity | spurious 每个 Linux IRQ 号对应一个 irq_desc,通过 irq_to_desc(irq) 获取

3.5 全流程协同:从硬件中断到驱动 handler

当硬件中断信号到达 CPU 时,中断子系统按以下流程协同工作:

  1. 硬件触发:中断控制器检测到管脚电平/边沿变化,向 CPU 发送 IRQ 信号。
  2. CPU 异常入口:CPU 保存上下文,跳转到 arch_irq_handler 汇编入口。
  3. irq_domain 查找:根据硬件中断号 (hwirq) 通过 irq_domain 找到对应的 Linux IRQ 号。
  4. irq_desc 调度:获取 irq_desc,调用 handle_irq 流控函数 (如 handle_edge_irq)。
  5. irq_chip 操作:流控函数调用 irq_chip 的 ack/mask/eoi 方法操作硬件。
  6. 执行 handler:遍历 action 链表,依次执行驱动注册的中断处理函数。
设计要点: 中断处理函数 (handler) 运行在中断上下文中,必须是原子的,不能睡眠、不能获取信号量、不能调用可能导致调度的函数。耗时操作应使用 中断下半部 (tasklet/workqueue/threaded irq) 处理。