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)在内核初始化时完成硬件配置。
GIC 硬件架构与中断分发流程 GIC 硬件架构与中断分发流程 外设中断源 软件触发中断 处理器间中断 分发器 使能/屏蔽 优先级仲裁 目标 CPU 选择 触发方式配置 CPU 接口 0 IRQ/FIQ 输出 CPU 接口 1 IRQ/FIQ 输出 CPU 0 CPU 1 说明: GIC 分发器统一管理所有中断,根据优先级和目标 CPU 配置,将中断路由到对应 CPU 接口。 每个 CPU 接口独立处理中断的应答(IAR)和结束(EOI)信号。

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。
irq_desc 数据结构关系 irq_desc 数据结构关系 struct irq_desc irq: 虚拟中断号 action: 处理函数链表 status: 状态标志 depth: 禁用深度 struct irq_data irq, hwirq chip: irq_chip 指针 struct irqaction handler: 上半部函数 thread_fn: 下半部线程 说明: irq_desc 通过 irq_data 关联硬件中断控制器操作,通过 irqaction 链表挂载驱动注册的中断处理函数。 共享中断时,多个 irqaction 节点通过 next 指针串联。

2.3 中断处理流程:上半部与下半部

中断处理必须快速完成,以避免阻塞其他中断或导致系统延迟。Linux 将中断处理拆分为两个阶段:

  • 上半部(Top Half):在中断上下文中立即执行,要求快速、不可睡眠。通常只完成最紧急的操作,如清除中断标志、拷贝硬件寄存器数据,然后触发下半部。
  • 下半部(Bottom Half):在更宽松的上下文中执行,允许睡眠,适合完成耗时处理。内核提供多种下半部机制:软中断(Softirq)、任务微调(Tasklet)、工作队列(Workqueue)和中断线程化(Threaded IRQ)。
驱动移植陷阱: 在中断处理函数中绝对不能调用可能导致睡眠的函数(如 kmalloc(GFP_KERNEL)、mutex_lock())。如果需要,必须将操作推迟到下半部或中断线程中执行。

2.3.1 标准中断处理流程

  1. 硬件外设触发中断信号,GIC 分发器仲裁后发送到目标 CPU。
  2. CPU 保存现场,跳转到内核的异常向量表,进入 handle_arch_irq 汇编入口。
  3. 调用 GIC 驱动提供的 gic_handle_irq(),读取中断号,调用 generic_handle_irq(irq)。
  4. 根据 irq_desc 找到注册的 irqaction,执行上半部处理函数 handler。
  5. 上半部返回后,检查是否需要触发下半部(如 tasklet_schedule、irq_work_queue)。
  6. 下半部在合适的时机(如软中断上下文或内核线程)执行 thread_fn 或 tasklet 函数。
Linux 中断处理流程:上半部与下半部 Linux 中断处理流程:上半部与下半部 硬件中断触发 GIC 分发与 CPU 应答 上半部 (Top Half) 中断上下文,不可睡眠 需要下半部? 是 下半部 (Bottom Half) 软中断 / 线程 / 工作队列 否 中断处理结束 CPU 恢复现场 说明: 上半部必须快速完成(通常微秒级),下半部可以延迟执行(毫秒级甚至更晚)。 驱动通过 request_threaded_irq() 注册上半部和下半部函数。

2.3.2 下半部机制对比

机制 上下文 可睡眠 并发模型 典型使用场景
软中断 (Softirq) 软中断上下文 否 同类型可多 CPU 并发 网络收发包、时钟 tick
Tasklet 软中断上下文 否 同类型串行执行 简单设备中断下半部
工作队列 (Workqueue) 进程上下文 是 可睡眠、可阻塞 复杂数据处理、I/O 操作
中断线程 (Threaded IRQ) 内核线程 是 每个中断独立线程 需要互斥锁、可能睡眠的驱动
实践建议: 对于大多数外设驱动,推荐使用 request_threaded_irq() 注册中断线程。上半部只做最小必要操作(如 readl() 读取状态寄存器),下半部在 thread_fn 中完成协议解析、数据拷贝等耗时任务。这种方式既保证了中断响应速度,又避免了在中断上下文中违规操作。