3. Linux 中断子系统架构

Linux 中断子系统是内核中管理硬件中断请求(IRQ)的核心模块,负责将外设的中断信号安全、高效地传递到对应的驱动程序处理函数。整个子系统采用分层架构设计,从下至上依次为:硬件层、核心层和驱动层。每一层职责清晰,通过标准接口进行交互,使得驱动开发者无需关心底层中断控制器的具体实现细节。

架构核心思想: 中断子系统通过抽象中断控制器(irq_chip)和中断描述符(irq_desc),实现了硬件无关性与驱动可移植性。驱动层只需调用 request_irq() / devm_request_irq() 注册中断处理函数,核心层负责路由与分发,硬件层负责与具体中断控制器(如 GIC、APIC、NVIC)交互。
Linux 中断子系统三层架构总览 Linux 中断子系统分层架构 驱动层 (Driver Layer) request_irq() / devm_request_irq() / free_irq() 中断处理函数 (handler) / 线程化中断 (request_threaded_irq) 核心层 (Core Layer) 中断描述符 (irq_desc) / 中断流控 (handle_irq) irq_domain / 中断映射 / 中断控制 (irq_chip 抽象) 硬件层 (Hardware Layer) 中断控制器 (GIC / APIC / NVIC / PIC) CPU 异常入口 / 中断向量表 / 硬件寄存器操作 驱动开发者关注层 内核通用中断管理 硬件平台相关层

3.1 中断子系统分层详解

中断子系统的三层结构各司其职,通过标准化的数据结构与函数接口进行协作。下表总结了每一层的核心职责、关键数据结构和主要接口函数。

分层 核心职责 关键数据结构 主要接口 / 函数
硬件层 直接操作中断控制器寄存器;处理 CPU 异常入口;完成硬件中断信号的接收与应答 中断控制器寄存器映射、中断向量表 irq_chip 回调(.irq_mask、.irq_unmask、.irq_ack、.irq_set_type)
核心层 管理中断描述符;实现中断流控;维护 irq_domain 映射;提供通用中断控制接口 struct irq_desc、struct irq_domain、struct irq_chip handle_irq()、generic_handle_irq()、irq_find_mapping()
驱动层 注册中断处理函数;实现设备特定的中断逻辑;管理中断使能与禁用 中断处理函数指针、设备上下文数据 request_irq()、devm_request_irq()、free_irq()、enable_irq()、disable_irq()
开发提示: 驱动开发者通常只需要与驱动层接口打交道。核心层和硬件层的细节由内核维护者或 BSP 工程师完成。理解分层结构有助于在调试中断问题时快速定位问题所在层。

3.2 中断描述符(irq_desc)结构

struct irq_desc 是中断子系统的核心数据结构,每个硬件中断源(IRQ)都对应一个 irq_desc 实例。它串联了硬件层的中断控制器操作、核心层的流控处理以及驱动层的中断处理函数。内核维护一个全局的 irq_desc 数组(或基数树),通过 IRQ 号索引。

irq_desc 结构体内部关系图 struct irq_desc 核心字段与关联 struct irq_desc irq_data / handle_irq / action / status / depth irq_data irq_chip *chip irq_domain *domain handle_irq 中断流控函数指针 handle_level_irq / handle_edge_irq action struct irqaction * 中断处理函数链表 irq_desc 关键字段说明 • irq_data: 包含中断控制器操作集 (irq_chip) 和映射域 (irq_domain) • handle_irq: 流控函数,决定中断是电平触发、边沿触发还是其他类型 • action: 指向 irqaction 链表,每个节点对应一个注册的中断处理函数

3.2.1 irq_desc 核心字段解析

以下列出 struct irq_desc 中最关键的几个字段,理解它们有助于掌握中断的完整处理流程。

  • irq_data:包含 irq_chip *chip(指向硬件操作函数集)和 irq_domain *domain(用于硬件 IRQ 号到 Linux IRQ 号的映射)。
  • handle_irq:中断流控函数指针,内核根据中断类型(电平、边沿、NMI 等)在初始化时设置为 handle_level_irq、handle_edge_irq 或 handle_fasteoi_irq 等。
  • action:指向 struct irqaction 链表的头指针。每个 irqaction 包含驱动注册的处理函数 handler、设备 ID dev_id、中断名称 name 等。多个驱动可以共享同一个 IRQ 号(如 PCI 设备共享中断)。
  • status / istate:中断状态标志位,如 IRQ_DISABLED、IRQ_INPROGRESS、IRQ_WAITING 等,用于内核内部状态管理。
  • depth:中断禁用深度计数器,每次调用 disable_irq() 递增,enable_irq() 递减,为 0 时中断才真正使能。
注意事项: 在共享中断(IRQF_SHARED)场景下,内核会遍历 action 链表,依次调用每个注册的处理函数。驱动必须通过 dev_id 参数判断中断是否来自自己的设备,并在不匹配时立即返回 IRQ_NONE。

3.2.2 irq_desc 生命周期与初始化流程

中断描述符的初始化发生在系统启动早期,由 early_irq_init() 和 init_IRQ() 完成。以下是典型初始化步骤:

  1. 静态分配或动态分配:内核通过 irq_desc_alloc() 分配 irq_desc 实例,通常以数组形式静态分配(NR_IRQS 大小),或使用基数树动态扩展。
  2. 设置默认流控函数:将 handle_irq 初始化为 handle_bad_irq,后续由中断控制器驱动根据硬件特性替换为正确的流控函数。
  3. 绑定 irq_chip:通过 irq_set_chip() 将 irq_data->chip 指向具体的中断控制器操作集(如 GIC、APIC 的 irq_chip 结构体)。
  4. 设置 irq_domain 映射:通过 irq_domain 建立硬件中断号(hwirq)到 Linux IRQ 号的映射关系。
  5. 驱动注册中断:驱动调用 request_irq() 时,内核将 irqaction 挂入 irq_desc->action 链表,并最终使能中断。
irq_desc 初始化与中断注册流程 irq_desc 初始化与驱动注册流程 early_irq_init() 分配 irq_desc 设置 irq_chip irq_domain 映射 request_irq() 挂入 action 链表 使能中断 流程说明:系统启动时完成 irq_desc 的分配与配置,驱动加载时通过 request_irq() 将处理函数挂入 action 链表 中断使能后,当硬件中断到达时,核心层通过 irq_desc 找到流控函数和处理函数并执行

3.3 中断处理路径:从硬件到驱动

当中断信号从外设到达 CPU 时,完整的处理路径如下:

  1. 硬件层:中断控制器(如 GIC)检测到中断,向 CPU 发送 IRQ 信号。CPU 保存上下文,跳转到中断向量表对应的入口地址。
  2. 核心层入口:汇编入口代码调用 handle_arch_irq() 或 generic_handle_irq(),根据 IRQ 号获取对应的 irq_desc。
  3. 流控处理:调用 irq_desc->handle_irq(如 handle_level_irq),执行中断屏蔽、应答、清除等硬件操作,并调用 handle_irq_event()。
  4. 驱动处理:handle_irq_event() 遍历 irq_desc->action 链表,依次执行每个 irqaction->handler(即驱动注册的中断处理函数)。
  5. 结束处理:流控函数执行中断取消屏蔽(unmask)或 EOI(End Of Interrupt)操作,恢复 CPU 中断状态。
关键代码路径示例: 驱动注册中断的典型代码片段如下:
/* 驱动中断处理函数 */
static irqreturn_t my_device_irq_handler(int irq, void *dev_id)
{
    struct my_device *dev = (struct my_device *)dev_id;
    /* 读取设备状态寄存器,判断中断来源 */
    uint32_t status = ioread32(dev->regs + STATUS_REG);
    if (status & RX_READY) {
        /* 处理接收数据 */
        ...
        return IRQ_HANDLED;
    }
    return IRQ_NONE; /* 非本设备中断 */
}

/* 驱动初始化时注册中断 */
static int my_device_probe(struct platform_device *pdev)
{
    struct my_device *dev;
    int irq, ret;

    irq = platform_get_irq(pdev, 0);
    if (irq < 0)
        return irq;

    dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
    if (!dev)
        return -ENOMEM;

    /* 注册中断,IRQF_TRIGGER_RISING 表示上升沿触发 */
    ret = devm_request_irq(&pdev->dev, irq, my_device_irq_handler,
                           IRQF_TRIGGER_RISING | IRQF_SHARED,
                           "my_device", dev);
    if (ret) {
        dev_err(&pdev->dev, "failed to request irq %d\n", irq);
        return ret;
    }
    return 0;
}

3.4 中断描述符与 irq_domain 映射

在现代嵌入式系统中,中断控制器可能包含大量中断源(如 GIC-400 支持 1024 个 SPI 中断)。irq_domain 是核心层提供的一种映射机制,用于将硬件中断号(hwirq)转换为Linux IRQ 号,并关联到对应的 irq_desc。常见的映射类型包括:

映射类型 描述 适用场景
线性映射 (linear) hwirq 直接作为数组索引,映射速度最快 中断源数量固定且较少(如 < 256)
树形映射 (tree) 使用基数树(radix tree)存储映射关系 中断源数量大且稀疏(如 GIC 的 SPI)
不映射 (no-map) hwirq 直接等于 Linux IRQ 号 简单嵌入式系统,中断号固定
调试技巧: 可以通过 /proc/interrupts 查看当前系统中所有注册的中断描述符信息,包括 IRQ 号、CPU 亲和性、中断次数以及注册的驱动名称。这是排查中断未触发或触发异常的首选工具。