2. Linux中断子系统架构

Linux内核的中断子系统是连接硬件外设与上层驱动软件的核心桥梁。理解其分层架构——从硬件中断信号到驱动层中断处理函数的完整路径——是进行中断相关参数调优的前提。本章将深入剖析中断控制器、中断描述符、IRQ号以及中断处理链四个核心组件。

2.1 中断控制器 (Interrupt Controller)

中断控制器是硬件层面管理中断请求的单元,负责接收来自外设的中断信号,进行优先级仲裁、屏蔽、使能,并将中断向量传递给CPU。在嵌入式系统中,常见的中断控制器包括GIC (Generic Interrupt Controller,用于ARM)、APIC (用于x86) 以及RISC-V的PLIC/CLINT。

中断控制器的主要职责包括:

  • 中断聚合与路由:将多个外设中断源汇聚,并根据配置路由到指定的CPU核心。
  • 优先级管理:为每个中断分配优先级,高优先级中断可以抢占低优先级中断的处理。
  • 中断屏蔽与使能:允许软件动态地开启或关闭特定中断源。
  • 中断类型配置:配置中断为电平触发(Level-triggered)或边沿触发(Edge-triggered)。
调优要点: 中断控制器的驱动参数(如GIC的优先级分组、中断亲和性(affinity)设置)直接影响系统的实时性和负载均衡。错误的配置可能导致中断风暴或CPU负载不均。
中断控制器在系统中的位置 外设 A (UART) 外设 B (GPIO) 外设 C (Timer) 中断控制器 (GIC / APIC) CPU 核心 (Core 0) 说明:外设产生中断信号 → 中断控制器仲裁/路由 → CPU响应并执行中断处理程序

2.2 中断描述符 (irq_desc)

在Linux内核中,每个中断源都对应一个struct irq_desc结构体,称为中断描述符。它是内核管理中断的软件抽象,包含了该中断的所有状态信息、配置参数以及处理函数链表。

irq_desc结构体的核心字段如下表所示:

字段名 类型 描述
irq_data struct irq_data 包含中断控制器相关的硬件信息(如chip、hwirq)
action struct irqaction * 指向中断处理动作链表的头指针
status_use_accessors unsigned int 中断状态标志(如IRQ_INPROGRESS、IRQ_DISABLED)
depth unsigned int 中断禁用深度计数,用于嵌套的disable_irq()调用
lock raw_spinlock_t 保护该描述符的自旋锁
percpu_enabled cpumask_var_t 用于per-CPU中断的CPU掩码
内核调试接口: 通过 /proc/interrupts 可以查看系统中所有中断描述符的统计信息,包括每个CPU上中断发生的次数,是诊断中断负载不均的第一手工具。

2.3 中断请求号 (IRQ Number)

IRQ号是内核为每个中断源分配的软件标识符,是驱动开发者与中断子系统交互的主要句柄。驱动通过request_irq()或devm_request_irq()函数注册中断处理程序时,第一个参数就是IRQ号。

IRQ号的分配机制分为静态和动态两种:

  • 静态分配:基于平台设备树(DT)或ACPI表,在系统启动时由中断控制器驱动解析并分配固定的IRQ号。
  • 动态分配:使用irq_alloc_descs()或irq_create_mapping()在运行时动态创建IRQ号与硬件中断的映射关系。

IRQ号与硬件中断线(HWIRQ)之间的映射关系由中断控制器驱动维护,驱动开发者通常只需要关注软件IRQ号。

IRQ号映射关系 HWIRQ 0 (SPI 32) HWIRQ 1 (PPI 14) 中断映射层 (irq_domain) IRQ #32 IRQ #29

2.4 中断处理链 (Interrupt Handling Chain)

当一个中断被触发时,内核并非直接调用驱动注册的处理函数,而是经过一条精心设计的处理链。这条链保证了中断处理的层次化、可扩展性和实时性。

中断处理链的主要阶段如下:

  1. 硬件中断入口:CPU接收到中断信号,保存上下文,跳转到内核定义的异常向量表入口。
  2. 中断控制器处理:调用handle_irq_desc(),根据IRQ号找到对应的irq_desc,并调用该描述符的handle_irq()回调(由中断控制器驱动设置,如handle_level_irq或handle_edge_irq)。
  3. 通用中断层处理:执行中断流控处理(如屏蔽中断、确认中断、重放等),然后遍历action链表。
  4. 驱动中断处理程序:依次调用链表上所有注册的irq_handler_t回调函数。如果处理函数返回IRQ_WAKE_THREAD,则唤醒对应的中断线程化处理函数。
  5. 退出与恢复:执行中断退出清理工作,恢复被中断的上下文。
性能陷阱: 在中断处理链的步骤4中,如果驱动注册的中断处理程序(尤其是非线程化的顶半部)执行时间过长,会阻塞后续所有中断的处理,导致系统延迟增大。这是调优时需要重点关注的环节。
Linux 中断处理链流程图 1. CPU 中断入口 2. 中断控制器处理 3. 通用中断层 (流控) 4. 遍历 action 链表 5a. 顶半部 (硬中断) 5b. 底半部 (线程化) 说明:顶半部执行时间必须极短,复杂操作应通过底半部(工作队列、软中断、tasklet)延迟处理。

2.5 中断处理链中的关键数据结构

中断处理链的核心数据结构是struct irqaction,它代表一个注册的中断处理动作。多个irqaction通过链表连接在同一个irq_desc的action字段上,实现了中断共享。

struct irqaction {
    irq_handler_t       handler;        /* 中断处理函数指针 (顶半部) */
    unsigned long       flags;          /* 中断标志 (IRQF_TRIGGER_*, IRQF_SHARED等) */
    void                *dev_id;        /* 设备ID,用于共享中断时区分设备 */
    struct irqaction    *next;          /* 指向链表中的下一个irqaction */
    irq_handler_t       thread_fn;      /* 线程化处理函数 (底半部) */
    struct task_struct  *thread;        /* 中断线程的task_struct指针 */
    struct irqaction    *secondary;     /* 用于级联中断 */
    unsigned int        irq;            /* 关联的IRQ号 */
    const char          *name;          /* 设备名称,显示在/proc/interrupts中 */
};
调优参数关联: irqaction中的flags字段决定了中断的触发方式(如IRQF_TRIGGER_HIGH)和是否共享。错误的触发方式配置会导致中断丢失或误触发,这是驱动调试中的常见问题。