2. Linux中断子系统架构:中断控制器(GIC)、中断描述符、中断处理流程
中断子系统是嵌入式Linux内核中与硬件交互最紧密、实时性要求最高的核心模块之一。本章深入剖析中断子系统的三大支柱:中断控制器(GIC)、中断描述符以及中断处理流程,帮助驱动开发者建立从硬件触发到软件处理的完整认知。
学习提示:理解中断子系统架构是正确配置设备树中断属性、编写高效中断服务程序(ISR)的前提。建议结合具体SoC手册(如i.MX8M、RK3588)的GIC章节对照学习。
2.1 中断控制器(GIC)—— 中断系统的中枢
在ARM Cortex-A系列多核处理器中,通用中断控制器(Generic Interrupt Controller, GIC)是管理所有中断源的核心硬件单元。它负责中断的优先级仲裁、分发到指定CPU核、以及中断屏蔽与使能。当前主流版本为GICv2和GICv3。
GIC逻辑上分为两大模块:
- Distributor(分发器):全局管理所有中断源,配置优先级、触发方式(电平/边沿)、目标CPU核。
- CPU Interface(CPU接口):每个CPU核独立拥有,负责与核内异常处理逻辑交互,提供中断应答、结束等控制。
GIC中断类型:
- SPI(Shared Peripheral Interrupt):共享外设中断,可路由到任意CPU核,中断号范围32~1019(GICv2通常到987)。
- PPI(Private Peripheral Interrupt):私有外设中断,每个CPU核独有,中断号16~31。
- SGI(Software Generated Interrupt):软件触发中断,用于核间通信(IPI),中断号0~15。
2.2 中断描述符(irq_desc)—— 内核的中断管理单元
在Linux内核中,每个中断源对应一个中断描述符(struct irq_desc),它是内核管理中断的核心数据结构。中断描述符串联了硬件中断号、中断服务程序链表、中断控制器操作函数集、以及中断状态标志。
中断描述符的主要字段与作用如下表所示:
| 字段 | 类型 | 作用说明 |
|---|---|---|
irq_data |
struct irq_data |
包含中断号、中断控制器芯片指针、硬件中断号、中断触发类型等核心信息。 |
action |
struct irqaction * |
指向中断服务程序链表的头节点,支持多个ISR共享同一中断线。 |
status_use_accessors |
unsigned int |
中断状态标志位,如 IRQ_DISABLED、IRQ_INPROGRESS、IRQ_WAKE_THREAD 等。 |
depth |
unsigned int |
中断禁用深度计数,用于嵌套禁用/使能场景。 |
irq_count |
unsigned int |
中断发生次数统计,用于调试和性能分析。 |
chip |
struct irq_chip * |
指向中断控制器芯片操作函数集,包括 irq_mask、irq_unmask、irq_ack、irq_set_type 等。 |
handle_irq |
irq_flow_handler_t |
高层的流控处理函数,如 handle_level_irq、handle_edge_irq,决定中断处理的标准流程。 |
驱动开发注意:中断描述符由内核在启动时根据硬件中断数量动态分配,驱动开发者通常不直接操作
irq_desc,而是通过 request_irq()、devm_request_irq() 等API间接使用。但在调试中断风暴或编写中断控制器驱动时,理解其内部结构至关重要。
2.3 中断处理流程 —— 从硬件触发到软件响应
一次完整的中断处理流程涉及硬件自动操作和内核软件处理两个阶段。以ARM GICv2为例,典型流程如下:
- 硬件阶段:中断触发与分发
- 外设拉高中断请求线(电平触发)或产生上升/下降沿(边沿触发)。
- GIC Distributor 根据配置的优先级和路由策略,选择目标CPU核。
- GIC CPU Interface 向目标CPU核发送IRQ信号,CPU核在异常向量表中跳转到
__irq_svc入口。
- 内核入口:保存现场与进入中断上下文
- CPU自动保存当前程序状态寄存器(CPSR)和返回地址到
lr_irq和spsr_irq。 - 内核汇编入口
__irq_svc保存所有通用寄存器,切换到中断栈,调用handle_arch_irq。
- CPU自动保存当前程序状态寄存器(CPSR)和返回地址到
- 中断控制器回调:应答与流控
handle_arch_irq读取GIC的GICC_IAR寄存器获取硬件中断号,调用对应的irq_desc.handle_irq流控函数。- 流控函数执行
chip->irq_ack应答中断,并根据触发类型决定是否屏蔽中断源。
- 执行中断服务程序(ISR)
- 遍历
irq_desc.action链表,依次调用注册的irqaction.handler。 - 如果ISR返回
IRQ_WAKE_THREAD,则唤醒对应的中断线程化处理函数。
- 遍历
- 退出中断:恢复现场与结束中断
- 流控函数调用
chip->irq_unmask解除中断屏蔽(边沿触发通常不需要)。 - 写入
GICC_EOIR寄存器通知GIC中断处理结束。 - 内核汇编代码恢复寄存器,执行
movs pc, lr返回被中断的上下文。
- 流控函数调用
2.4 中断处理流程的关键机制
为了满足实时性和吞吐量要求,Linux内核在中断处理中引入了几个关键机制:
| 机制 | 描述 | 适用场景 |
|---|---|---|
| 中断线程化 | 将ISR中耗时操作推迟到内核线程执行,降低关中断时间。 | I2C/SPI总线传输、块设备请求、网络协议栈处理。 |
| 顶半部 / 底半部 | 顶半部(hardirq)快速应答硬件,底半部(softirq/tasklet/workqueue)处理剩余工作。 | 网卡收包、USB数据传输、定时器回调。 |
| 中断共享 | 多个设备共用同一中断线,通过 dev_id 区分。 |
PCI设备、部分SoC内部外设。 |
| 中断嵌套 | 高优先级中断可以抢占低优先级中断的处理。 | 实时系统、紧急事件处理。 |
性能指标参考:
- 从硬件中断触发到ISR开始执行,典型延迟在 1~10 μs 级别(取决于CPU频率和缓存状态)。
- 中断线程化会增加额外上下文切换开销,约 5~20 μs。
- 顶半部应尽量短小,通常控制在 50 μs 以内,避免影响其他中断的响应。
2.5 中断描述符与设备树的关联
设备树中的中断属性最终会被内核解析并填充到中断描述符中。关键映射关系如下:
interrupt-parent:指定中断控制器节点,对应irq_desc.chip指向的中断控制器驱动。interrupts:描述中断号、触发类型和标志,解析后填充irq_desc.irq_data中的hwirq和trigger_type。interrupt-names:为中断号提供别名,便于驱动通过platform_get_irq_byname()获取。
设备树中断属性到中断描述符的转换流程:
- 内核解析设备树,调用
of_irq_parse_one()提取中断控制器和硬件中断号。 - 调用
irq_create_of_mapping()将硬件中断号映射为Linux虚拟中断号(virq)。 - 内核分配或查找对应的
irq_desc,初始化irq_data和chip指针。 - 驱动调用
request_irq(virq, handler, flags, name, dev)将ISR挂载到irq_desc.action链表。
常见错误:设备树中
interrupts 属性的中断号填写错误,或 interrupt-parent 指向错误的控制器节点,会导致 platform_get_irq() 返回 -EINVAL 或 -EPROBE_DEFER。务必对照SoC手册确认中断号偏移(GIC通常从32开始计数)。