2. Linux 中断子系统架构:中断控制器、中断描述符、中断处理流程

中断子系统是 Linux 内核响应硬件事件的神经中枢。本章从全局视角拆解其三大核心组件:中断控制器(Interrupt Controller)、中断描述符(Interrupt Descriptor)以及中断处理流程(Interrupt Handling Flow)。理解这三者的协作关系,是后续掌握 tasklet 与工作队列的前提。

2.1 中断控制器:硬件与内核的桥梁

中断控制器负责收集外设的中断请求,进行优先级仲裁,并通知 CPU。在嵌入式系统中,常见的中断控制器包括 GIC(Generic Interrupt Controller,ARM)、APIC(x86)和 INTC(RISC-V)。

内核通过 struct irq_chip 抽象中断控制器的硬件操作,包括中断使能、屏蔽、确认和触发方式配置。

中断控制器类型 典型架构 关键特性
GIC v2/v3/v4 ARM Cortex-A/R 支持 SPI、PPI、SGI;支持亲和性分配
APIC / x2APIC x86 支持 I/O APIC 与本地 APIC;MSI 中断
PLIC / CLINT RISC-V 平台级中断控制器;上下文切换开销低
实践提示:在设备树中,interrupt-parent 属性指向中断控制器节点,interrupts 属性指定中断号与触发类型。驱动开发中,务必确认中断控制器驱动已正确注册。
中断控制器与 CPU 连接关系 外设 A 外设 B 外设 C 中断控制器 GIC / APIC / PLIC CPU 核心 IRQ 线 IRQ 线共享与亲和性

2.2 中断描述符:内核的中断管理单元

每个中断源在内核中对应一个 struct irq_desc 描述符。该结构体是中断管理的核心,包含中断状态、处理函数链表、芯片操作指针和线程化标志。

// include/linux/irqdesc.h (简化结构)
struct irq_desc {
    struct irq_common_data  irq_common_data;
    struct irq_chip         *irq_chip;      // 中断控制器操作
    struct irqaction        *action;        // 处理函数链表
    unsigned int            status_use_accessors;
    const char              *name;
    struct proc_dir_entry   *dir;
    unsigned int            irq;
    // ... 其他字段
};

中断描述符通过 irq_to_desc(irq) 宏快速查找,内核启动时根据硬件拓扑预分配。每个描述符的 action 链表支持多个处理函数共享同一中断号(共享中断)。

关键字段说明:
  • irq_chip:指向中断控制器的硬件操作函数集。
  • action:挂载该中断的所有处理函数,以单向链表组织。
  • status_use_accessors:记录中断是否被屏蔽、是否正在处理等状态。
中断描述符 irq_desc 结构 struct irq_desc irq_chip → 芯片操作 action → 处理链表 status → 状态标志 name → 中断名称 struct irq_chip irq_mask / irq_unmask struct irqaction handler → 处理函数 每个中断号对应 一个 irq_desc

2.3 中断处理流程:从硬件触发到软件执行

一次完整的中断处理分为三个阶段:硬件阶段、内核入口阶段和执行阶段。理解这个流程是编写高效中断处理程序的基础。

  1. 硬件阶段:外设拉高中断线,中断控制器仲裁后向 CPU 发送 IRQ 信号。
  2. 内核入口阶段:CPU 保存上下文,跳转到 irq_handler 入口,调用 handle_irq_desc()。
  3. 执行阶段:根据中断描述符的 action 链表,依次执行注册的处理函数。处理函数分为顶半部(快速响应)和底半部(延迟处理)。
性能陷阱:顶半部执行期间,当前中断线被屏蔽(取决于触发类型),过长的顶半部会直接导致中断延迟增大。建议顶半部只做最小必要操作,将耗时工作推迟到底半部(tasklet / 工作队列)。
中断处理流程(简化) 外设触发中断 中断控制器仲裁 CPU 保存上下文 执行顶半部处理 底半部延迟执行 顶半部必须快 不可睡眠

2.4 中断处理流程中的关键函数调用链

从 CPU 接收到中断信号到执行具体处理函数,内核内部经历了一系列精心设计的函数调用。以下是 ARM64 架构下的典型调用链:

中断触发
  → 异常向量表 entry (arch/arm64/kernel/entry.S)
    → handle_arch_irq()  (跳转到中断控制器入口)
      → gic_handle_irq()  (GIC 驱动)
        → handle_domain_irq()
          → __handle_domain_irq()
            → generic_handle_irq_desc()
              → desc->handle_irq()  (流控处理函数)
                → handle_edge_irq() / handle_level_irq()
                  → handle_irq_event()
                    → __handle_irq_event_percpu()
                      → action->handler()  (驱动注册的处理函数)
关键点:
  • desc->handle_irq 是流控处理函数,根据中断触发类型(边沿/电平)选择不同策略。
  • handle_irq_event 负责遍历 action 链表,依次调用所有注册的处理函数。
  • 处理函数返回 IRQ_HANDLED 或 IRQ_NONE,内核据此判断是否发生伪中断。

2.5 中断处理流程的同步与并发考量

中断上下文与进程上下文存在本质区别,驱动开发中必须遵守以下规则:

特性 中断上下文(顶半部) 进程上下文
可睡眠 ❌ 禁止 ✅ 允许
持有锁 只能使用 spinlock 可以使用 mutex
调度 不可调度 可被调度
访问用户空间 ❌ 禁止 ✅ 允许
常见错误:在顶半部中使用 kmalloc(GFP_KERNEL) 或 mutex_lock() 会导致内核崩溃。顶半部只能使用 GFP_ATOMIC 分配内存,并使用 spinlock 保护共享数据。

2.6 本章小结

中断子系统架构围绕“中断控制器 → 中断描述符 → 处理流程”三条主线展开:

  • 中断控制器抽象硬件差异,提供统一的芯片操作接口。
  • 中断描述符是每个中断号在内核中的“档案”,管理处理函数与状态。
  • 处理流程从硬件触发到软件执行,严格区分顶半部与底半部,确保系统实时性。