2. Linux 中断子系统架构
中断是嵌入式 Linux 系统中响应外部或内部异步事件的核心机制。理解中断子系统架构,是进行驱动中断申请与移植的基础。本章从硬件控制器、内核描述符和软件处理流程三个层次,剖析中断子系统的全貌。
2.1 中断控制器(GIC)
在 ARM 多核处理器平台上,通用中断控制器(Generic Interrupt Controller, GIC)是中断管理的硬件核心。GIC 负责接收来自外设、软件触发或处理器间的中断信号,进行优先级仲裁,并分发到指定的 CPU 核心。
GIC 架构主要分为两个逻辑单元:
- 分发器(Distributor):集中管理所有中断源,控制每个中断的使能、优先级、触发方式(电平/边沿)和目标 CPU。
- CPU 接口(CPU Interface):每个 CPU 核心独立拥有一个接口,负责向核心发送中断请求信号(IRQ/FIQ),并处理中断应答与结束。
驱动开发者视角: 在设备树中,
interrupt-parent 属性指向 GIC 节点,interrupts 属性描述中断号、触发类型和优先级。GIC 的驱动(如 drivers/irqchip/irq-gic.c)在内核初始化时完成硬件配置。
2.2 中断描述符(irq_desc)
在内核中,每个硬件中断源对应一个 struct irq_desc 结构体,称为中断描述符。它是软件层面管理中断的核心数据结构,串联起硬件中断号、中断处理函数、中断控制器操作和线程化处理等机制。
| 字段 | 类型 | 描述 |
|---|---|---|
irq_data |
struct irq_data |
包含中断号 irq、硬件中断号 hwirq、中断控制器 chip 指针 |
action |
struct irqaction * |
指向中断处理函数链表的头指针,支持共享中断 |
status_use_accessors |
unsigned int |
中断状态标志,如 IRQ_DISABLED、IRQ_INPROGRESS |
depth |
unsigned int |
中断禁用深度计数,用于嵌套 disable_irq / enable_irq |
threads_oneshot |
atomic_t |
跟踪正在运行的中断线程,用于 oneshot 同步 |
percpu_enabled |
cpumask_var_t |
Per-CPU 中断的 CPU 使能掩码 |
关键概念: 中断描述符通过
irq_to_desc(irq) 宏根据虚拟中断号查找。在设备树中,interrupts = <GIC_SPI 42 IRQ_TYPE_LEVEL_HIGH> 中的 42 是硬件中断号,内核会将其映射到对应的 irq_desc。
2.3 中断处理流程:上半部与下半部
中断处理必须快速完成,以避免阻塞其他中断或导致系统延迟。Linux 将中断处理拆分为两个阶段:
- 上半部(Top Half):在中断上下文中立即执行,要求快速、不可睡眠。通常只完成最紧急的操作,如清除中断标志、拷贝硬件寄存器数据,然后触发下半部。
- 下半部(Bottom Half):在更宽松的上下文中执行,允许睡眠,适合完成耗时处理。内核提供多种下半部机制:软中断(Softirq)、任务微调(Tasklet)、工作队列(Workqueue)和中断线程化(Threaded IRQ)。
驱动移植陷阱: 在中断处理函数中绝对不能调用可能导致睡眠的函数(如
kmalloc(GFP_KERNEL)、mutex_lock())。如果需要,必须将操作推迟到下半部或中断线程中执行。
2.3.1 标准中断处理流程
- 硬件外设触发中断信号,GIC 分发器仲裁后发送到目标 CPU。
- CPU 保存现场,跳转到内核的异常向量表,进入
handle_arch_irq汇编入口。 - 调用 GIC 驱动提供的
gic_handle_irq(),读取中断号,调用generic_handle_irq(irq)。 - 根据
irq_desc找到注册的irqaction,执行上半部处理函数handler。 - 上半部返回后,检查是否需要触发下半部(如
tasklet_schedule、irq_work_queue)。 - 下半部在合适的时机(如软中断上下文或内核线程)执行
thread_fn或tasklet函数。
2.3.2 下半部机制对比
| 机制 | 上下文 | 可睡眠 | 并发模型 | 典型使用场景 |
|---|---|---|---|---|
| 软中断 (Softirq) | 软中断上下文 | 否 | 同类型可多 CPU 并发 | 网络收发包、时钟 tick |
| Tasklet | 软中断上下文 | 否 | 同类型串行执行 | 简单设备中断下半部 |
| 工作队列 (Workqueue) | 进程上下文 | 是 | 可睡眠、可阻塞 | 复杂数据处理、I/O 操作 |
| 中断线程 (Threaded IRQ) | 内核线程 | 是 | 每个中断独立线程 | 需要互斥锁、可能睡眠的驱动 |
实践建议: 对于大多数外设驱动,推荐使用
request_threaded_irq() 注册中断线程。上半部只做最小必要操作(如 readl() 读取状态寄存器),下半部在 thread_fn 中完成协议解析、数据拷贝等耗时任务。这种方式既保证了中断响应速度,又避免了在中断上下文中违规操作。