3、Linux中断子系统架构:中断描述符、irq_chip、中断处理函数注册机制

Linux中断子系统是内核中负责管理硬件中断请求的核心模块,其设计遵循分层与抽象原则,使得上层驱动开发者无需关心底层中断控制器的具体实现细节。理解中断描述符(irq_desc)、中断控制器抽象(irq_chip)以及中断处理函数的注册机制,是掌握中断亲和性与共享中断故障排查的基础。

3.1 中断描述符(irq_desc)——中断的“身份证”

每个中断源在内核中对应一个irq_desc结构体实例,该结构体集中管理该中断的所有状态与回调信息。系统启动时,内核根据平台支持的最大中断数(NR_IRQS)预分配一个irq_desc数组。

核心字段说明:
  • irq_data:包含irq_chip指针、硬件中断号、中断控制器私有数据。
  • action:指向irqaction链表的头指针,一个中断可挂载多个处理函数(共享中断)。
  • status_use_accessors:中断状态标志位,如IRQ_DISABLED、IRQ_INPROGRESS。
  • depth:中断禁用深度计数,用于嵌套禁用/启用场景。
  • lock:自旋锁,保护该描述符的并发访问。
irq_desc 结构体核心布局 struct irq_desc irq_data action (链表头) status_use_accessors depth lock (自旋锁) handle_irq (入口函数) 说明:action 链表支持多个 irqaction 节点,实现共享中断。 handle_irq 由高层中断控制器设置,决定中断处理流程。

3.2 irq_chip——中断控制器的软件抽象

irq_chip结构体是内核为不同中断控制器(如APIC、GIC、IO-APIC)提供的统一接口层。每个irq_chip实例封装了操作底层中断控制器的回调函数指针,使得中断子系统核心代码可以以多态方式操作硬件。

回调函数 功能描述 典型实现场景
irq_mask 屏蔽中断源(禁止传递) 写中断控制器屏蔽寄存器
irq_unmask 解除屏蔽中断源 写中断控制器解除屏蔽寄存器
irq_ack 应答中断(清除挂起状态) 写EOI(End of Interrupt)寄存器
irq_set_affinity 设置中断亲和性(CPU掩码) 写中断重定向表项(如MSI-X Table)
irq_set_type 配置中断触发方式(电平/边沿) 配置中断控制器触发寄存器
排查要点:当发现中断无法正确路由到目标CPU时,应首先检查irq_chip中的irq_set_affinity回调是否被正确实现。部分老旧或模拟中断控制器可能不支持亲和性设置,导致/proc/irq/N/smp_affinity写入无效。
irq_chip 抽象层与硬件控制器映射 中断子系统核心 (kernel/irq/) struct irq_chip 回调接口 APIC (x86) GIC (ARM) IO-APIC (x86)

3.3 中断处理函数注册机制

设备驱动通过request_irq()或request_threaded_irq()向内核注册中断处理函数。注册的核心操作是将irqaction结构体挂载到对应irq_desc的action链表上。

3.3.1 注册流程关键步骤

  1. 参数校验:检查中断号有效性、处理函数指针非空、标志位合法。
  2. 分配irqaction:内核分配并初始化irqaction结构体,填充处理函数指针、设备ID、中断名称、标志位(IRQF_SHARED等)。
  3. 获取irq_desc锁:调用__setup_irq()内部函数,持有irq_desc->lock自旋锁。
  4. 检查共享兼容性:如果中断已被注册,新注册必须设置IRQF_SHARED标志,且触发方式必须一致。
  5. 挂载到链表:将新irqaction插入action链表尾部。
  6. 配置中断控制器:如果这是该中断的第一个处理函数,调用irq_chip的irq_startup或irq_unmask启用中断。
共享中断注册陷阱:所有共享同一中断号的驱动必须使用IRQF_SHARED标志,且中断触发方式(电平/边沿)必须完全一致。边沿触发的中断在共享场景下极易丢失中断,推荐共享中断使用电平触发方式。
/* 典型的中断注册示例 */
int devm_request_irq(struct device *dev, unsigned int irq,
                     irq_handler_t handler, unsigned long irqflags,
                     const char *devname, void *dev_id)
{
    /* 1. 参数校验 */
    if (!handler || irq < 0)
        return -EINVAL;

    /* 2. 分配并初始化 irqaction */
    struct irqaction *action = kzalloc(sizeof(*action), GFP_KERNEL);
    action->handler = handler;
    action->flags = irqflags;
    action->name = devname;
    action->dev_id = dev_id;

    /* 3. 调用内部注册函数 */
    return __setup_irq(irq, action, IRQF_COND_ONESHOT);
}

3.3.2 中断处理函数执行路径

当中断触发时,内核通过irq_desc->handle_irq进入通用中断处理层,然后遍历action链表,依次调用每个注册的处理函数。对于共享中断,内核会逐个调用直到某个处理函数返回IRQ_HANDLED或遍历完整个链表。

中断处理函数注册与执行流程 注册阶段 (request_irq) 1. 分配 irqaction 结构体 2. 挂载到 irq_desc->action 链表 3. 调用 irq_chip->irq_unmask 启用中断 执行阶段 (中断触发) 1. CPU 跳转到中断向量入口 2. 调用 handle_irq 遍历 action 链表 3. 依次执行各 irqaction->handler 共享中断执行关键行为 • 内核按链表顺序调用所有注册的处理函数,直到某个函数返回 IRQ_HANDLED。 • 如果所有函数都返回 IRQ_NONE,内核记录“spurious interrupt”警告。 • 处理函数必须通过 dev_id 参数判断中断是否属于自己的设备,避免误处理。

3.4 中断描述符与irq_chip的关联

irq_desc通过内嵌的irq_data结构体与irq_chip建立关联。irq_data包含chip指针指向具体的irq_chip实例,以及chip_data指针指向中断控制器的私有数据。这种设计使得中断子系统核心代码可以统一调用irq_chip的回调,而无需关心底层硬件差异。

排查关联性故障:
  • 如果/proc/interrupts中某中断的“chip”列显示为IO-APIC或GIC-v3等名称,说明irq_chip已正确关联。
  • 若显示None或unknown,通常表示中断控制器驱动未正确初始化,或设备树/ACPI表中缺少中断控制器节点。
  • 亲和性设置失败时,检查irq_chip->irq_set_affinity是否为NULL,若为NULL则内核会返回-EINVAL。