2. Linux中断子系统框架:从硬件中断到request_irq()的完整链路

理解Linux中断子系统是嵌入式驱动开发的核心能力。本章将拆解一条完整的中断处理链路:从硬件引脚上的电平跳变,到驱动程序中request_irq()注册的回调函数被安全调用。这条链路涉及硬件控制器、内核通用层、架构相关代码和驱动接口四个层次。

2.1 中断子系统四层架构总览

Linux中断子系统采用分层设计,每一层屏蔽下层细节,为上层提供抽象接口。下表总结了四个层次及其核心职责:

层次 核心组件 主要职责 典型文件/API
硬件层 中断控制器(GIC/APIC/INTC) 检测中断信号、优先级仲裁、向CPU发送IRQ 芯片手册、设备树
架构层 CPU异常向量表、entry.S 保存上下文、跳转到通用入口 arch/arm/kernel/entry-armv.S
通用中断层 irq_desc、irq_chip、irq_domain 中断号映射、控制流管理、调用链执行 kernel/irq/*.c
驱动层 struct irqaction、handler 注册中断处理函数、实现设备业务逻辑 request_irq()、devm_request_irq()
Linux中断子系统四层架构与数据流 硬件层:中断控制器 (GIC/APIC/INTC) 检测中断信号 → 优先级仲裁 → 向CPU发送IRQ 架构层:CPU异常向量表 / entry.S 保存寄存器上下文 → 获取中断号 → 跳转到通用入口 通用中断层:irq_desc / irq_chip / irq_domain 中断号映射 → 控制流管理 → 调用链执行 驱动层:struct irqaction / handler request_irq()注册 → 中断处理函数执行 → 设备业务逻辑 数据流方向:硬件中断信号 → CPU异常入口 → 通用中断框架 → 驱动回调函数

2.2 硬件中断信号到CPU异常入口

当外设产生中断时,信号路径如下:

  1. 中断控制器检测:外设拉高/拉低中断请求线,中断控制器(如GIC-400)采样该信号。
  2. 优先级与分发:中断控制器比较中断优先级,若高于当前CPU屏蔽等级,则向CPU核发送IRQ信号。
  3. CPU异常响应:CPU完成当前指令后,检测到IRQ引脚有效,进入中断异常模式。
  4. 硬件自动保存:CPU硬件自动保存PC、CPSR到中断模式下的LR和SPSR,并跳转到异常向量表对应入口。
关键概念:中断号(IRQ Number)
硬件中断控制器为每个中断源分配一个硬件中断号(hwirq)。Linux通用中断层通过irq_domain将hwirq映射为内核使用的Linux IRQ号(virq)。驱动开发中,request_irq()的第一个参数就是virq。

2.3 架构层:entry.S与中断入口

以ARM架构为例,中断异常向量入口在arch/arm/kernel/entry-armv.S中定义。核心流程如下:

/* 简化后的ARM中断入口流程 */
vector_irq:
    /* 1. 保存被中断上下文(SVC模式寄存器) */
    SAVE_ALL

    /* 2. 获取中断号,调用通用中断处理 */
    get_irqnr_and_base r0, lr, r1, r2
    movne   r1, #0
    bne     asm_do_IRQ

    /* 3. 恢复上下文并返回 */
    restore_irq_regs
    movs    pc, lr          @ 从IRQ模式返回

这段汇编代码完成了三个关键动作:

  • SAVE_ALL:将R0-R12、LR、SPSR压入栈中,确保中断处理不会破坏被中断任务的寄存器状态。
  • get_irqnr_and_base:读取中断控制器寄存器,获取当前最高优先级中断的硬件号。
  • asm_do_IRQ:调用C语言函数,进入通用中断处理层。

2.4 通用中断层核心数据结构

通用中断层是中断子系统的中枢,围绕三个核心数据结构运转:

数据结构 作用 关键字段
struct irq_desc 每个Linux IRQ号对应一个描述符,管理该中断的所有状态和回调 irq_data、action链表、thread_flags
struct irq_chip 封装中断控制器的硬件操作(屏蔽、使能、应答、触发类型设置) irq_mask、irq_unmask、irq_ack
struct irq_domain 建立硬件中断号(hwirq)到Linux IRQ号(virq)的映射关系 ops->map、linear_revmap
通用中断层核心数据结构关系 struct irq_domain hwirq → virq 映射 linear_revmap[] ops->map / ops->unmap struct irq_desc irq_data (含chip指针) action (irqaction链表) thread_flags / depth struct irq_chip irq_mask / irq_unmask irq_ack / irq_eoi irq_set_type 映射 操作 中断处理调用链(通用层内部) 1. asm_do_IRQ(irq) → 根据irq号找到对应的irq_desc 2. 调用 irq_desc->irq_data.chip->irq_ack() 应答中断(硬件操作) 3. 遍历 irq_desc->action 链表,依次执行每个 irqaction->handler() 4. 若中断被标记为 IRQF_SHARED,继续检查下一个action 5. 所有handler执行完毕后,调用 chip->irq_unmask() 重新使能中断

2.5 驱动层:request_irq()注册链路

驱动开发者通过request_irq()将中断处理函数挂载到中断子系统。该函数的原型和参数如下:

int request_irq(unsigned int irq,               /* Linux IRQ号 */
                irq_handler_t handler,           /* 中断处理函数 */
                unsigned long flags,             /* 中断标志 */
                const char *name,                /* 设备名称 */
                void *dev_id);                   /* 设备ID(共享中断时用于区分) */

request_irq()的内部执行流程分为以下步骤:

  1. 参数校验:检查irq号是否合法、handler是否为NULL、flags是否包含冲突选项。
  2. 分配irqaction:内核分配一个struct irqaction结构体,填充handler、flags、name、dev_id等字段。
  3. 设置触发类型:调用irq_set_irq_type(),通过irq_chip的irq_set_type回调配置中断控制器寄存器(上升沿/下降沿/高电平/低电平)。
  4. 挂载到irq_desc:将irqaction添加到irq_desc->action链表中。如果是共享中断,则插入链表尾部。
  5. 使能中断:调用irq_startup()或enable_irq(),最终调用irq_chip的irq_unmask回调,允许中断控制器向CPU传递该中断。
开发建议: 在嵌入式系统中,优先使用devm_request_irq()替代request_irq()。前者将中断资源的释放绑定到设备生命周期,当设备驱动卸载或探测失败时自动调用free_irq(),避免资源泄漏。

2.6 完整链路时序总结

从硬件中断触发到驱动回调执行,完整时序如下表所示:

步骤 执行位置 动作描述 耗时参考
1 外设/中断控制器 外设拉高中断请求线,GIC采样并仲裁 ~50ns
2 CPU核 CPU检测IRQ引脚,保存PC/CPSR,跳转异常向量 ~10ns
3 entry.S (架构层) SAVE_ALL保存寄存器,读取中断号 ~100ns
4 asm_do_IRQ (通用层) 查找irq_desc,调用irq_ack应答 ~50ns
5 handle_irq_event (通用层) 遍历action链表,执行handler 取决于handler复杂度
6 通用层/架构层 调用irq_eoi/unmask,恢复上下文并返回 ~80ns
性能关注点: 步骤3和步骤6的上下文保存/恢复开销是固定的,约占总中断延迟的30%~50%。对于高频中断(如网络、存储控制器),应尽量减少handler执行时间,或使用中断线程化(IRQF_ONESHOT + request_threaded_irq)将耗时处理推入内核线程。

2.7 中断标志位flags详解

request_irq()的flags参数决定了中断的行为模式。以下是嵌入式开发中最常用的标志位:

标志位 值 作用 使用场景
IRQF_TRIGGER_RISING 0x00000001 上升沿触发 按键中断、GPIO边沿检测
IRQF_TRIGGER_FALLING 0x00000002 下降沿触发 按键中断、GPIO边沿检测
IRQF_TRIGGER_HIGH 0x00000004 高电平触发 电平敏感型外设(如某些UART)
IRQF_TRIGGER_LOW 0x00000008 低电平触发 电平敏感型外设
IRQF_SHARED 0x00000080 允许多个设备共享同一IRQ线 PCI设备、部分SoC内部外设
IRQF_ONESHOT 0x00002000 中断处理期间硬件屏蔽中断,handler返回后自动恢复 中断线程化(配合request_threaded_irq)
IRQF_NO_SUSPEND 0x00004000 系统休眠时不关闭该中断 唤醒源中断(如RTC闹钟)
request_irq() 内部调用流程图 request_irq() setup_irq() __setup_irq() irq_startup() chip->irq_unmask() __setup_irq():分配irqaction、设置触发类型、挂载到irq_desc->action链表 irq_startup() → chip->irq_unmask():最终使能硬件中断线

2.8 本章小结

  • Linux中断子系统分为硬件层、架构层、通用中断层、驱动层四层,每层职责清晰,接口明确。
  • 从硬件中断到request_irq()回调的完整链路涉及中断控制器仲裁、CPU异常向量、通用层数据结构映射和驱动回调注册。
  • irq_domain是连接硬件中断号与Linux IRQ号的桥梁,在设备树和ACPI系统中尤为重要。
  • request_irq()内部通过setup_irq()和__setup_irq()完成irqaction的分配、挂载和中断使能。
  • 理解flags参数(触发类型、共享、oneshot)是正确配置中断行为的前提。