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() |
2.2 硬件中断信号到CPU异常入口
当外设产生中断时,信号路径如下:
- 中断控制器检测:外设拉高/拉低中断请求线,中断控制器(如GIC-400)采样该信号。
- 优先级与分发:中断控制器比较中断优先级,若高于当前CPU屏蔽等级,则向CPU核发送IRQ信号。
- CPU异常响应:CPU完成当前指令后,检测到IRQ引脚有效,进入中断异常模式。
- 硬件自动保存:CPU硬件自动保存PC、CPSR到中断模式下的LR和SPSR,并跳转到异常向量表对应入口。
关键概念:中断号(IRQ Number)
硬件中断控制器为每个中断源分配一个硬件中断号(hwirq)。Linux通用中断层通过
硬件中断控制器为每个中断源分配一个硬件中断号(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 |
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()的内部执行流程分为以下步骤:
- 参数校验:检查irq号是否合法、handler是否为NULL、flags是否包含冲突选项。
- 分配irqaction:内核分配一个
struct irqaction结构体,填充handler、flags、name、dev_id等字段。 - 设置触发类型:调用
irq_set_irq_type(),通过irq_chip的irq_set_type回调配置中断控制器寄存器(上升沿/下降沿/高电平/低电平)。 - 挂载到irq_desc:将irqaction添加到
irq_desc->action链表中。如果是共享中断,则插入链表尾部。 - 使能中断:调用
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闹钟) |
2.8 本章小结
- Linux中断子系统分为硬件层、架构层、通用中断层、驱动层四层,每层职责清晰,接口明确。
- 从硬件中断到
request_irq()回调的完整链路涉及中断控制器仲裁、CPU异常向量、通用层数据结构映射和驱动回调注册。 irq_domain是连接硬件中断号与Linux IRQ号的桥梁,在设备树和ACPI系统中尤为重要。request_irq()内部通过setup_irq()和__setup_irq()完成irqaction的分配、挂载和中断使能。- 理解flags参数(触发类型、共享、oneshot)是正确配置中断行为的前提。