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

中断子系统是 Linux 内核响应外部异步事件的基石。在 ARM 多核处理器平台上,中断控制器(Generic Interrupt Controller, GIC)负责收集、优先级排序和分发中断信号;内核通过中断描述符(irq_desc)将硬件中断号映射到软件处理逻辑;整个处理流程分为上半部(硬中断)和下半部(软中断/tasklet)。本章从架构层面拆解这三个核心要素。

核心要点:中断子系统 = GIC(硬件路由) + irq_desc(软件抽象) + 处理流程(上下半部分离)。

2.1 中断控制器(GIC)

GIC 是 ARM 架构的标准中断控制器,负责接收外设中断信号,按优先级排序后分发到指定的 CPU 核心。Linux 内核通过 drivers/irqchip/ 下的驱动对 GIC 进行初始化与配置。

2.1.1 GIC 版本与特性对比

特性 GICv2 GICv3 GICv4
CPU 接口 内存映射寄存器(GICC) 系统寄存器(ICC_*) 系统寄存器 + 虚拟化扩展
中断号范围 0~1019 0~8191 0~8191
多核分发 SPI 支持亲和性 支持 SPI + LPI 亲和性 支持直接注入虚拟机
LPI(消息中断) 不支持 支持(ITS) 支持(ITS + vLPI)
虚拟化支持 基本(vgic) 增强(vgic-v3) 直接 VM 中断注入
工程提示:在嵌入式 Linux 开发中,GICv2 仍广泛用于 Cortex-A7/A9 平台;GICv3 是 ARMv8 的标配;GICv4 主要面向服务器和虚拟化场景。驱动开发者通常不需要直接操作 GIC 寄存器,而是通过 irqchip 框架注册回调。

2.1.2 GIC 中断类型

  • SGI(Software Generated Interrupt):软件触发的中断,用于核间通信(IPI),中断号 0~15。
  • PPI(Private Peripheral Interrupt):私有外设中断,每个 CPU 核心独享,中断号 16~31。
  • SPI(Shared Peripheral Interrupt):共享外设中断,可路由到任意 CPU 核心,中断号 32~1019(GICv2)。
  • LPI(Locality-specific Peripheral Interrupt):基于消息的中断,通过 ITS(Interrupt Translation Service)路由,中断号 8192 起。
避坑指南:在设备树中配置中断时,注意 interrupt-cells 属性:GICv2 通常为 3(中断类型、中断号、触发方式),GICv3 为 3 或 4。类型标志 0 表示 SPI,1 表示 PPI。

2.2 中断描述符(irq_desc)

内核使用 struct irq_desc 描述每个中断源。该结构体是中断子系统的核心抽象,连接硬件中断号与软件处理函数。

2.2.1 irq_desc 关键字段

字段 类型 说明
irq_data struct irq_data 硬件相关数据(中断号、chip、chip_data)
action struct irqaction * 中断处理函数链表(共享中断时多个 handler)
status_use_accessors unsigned int 中断状态标志(IRQ_DISABLED、IRQ_INPROGRESS 等)
depth unsigned int 中断禁用深度计数
percpu_enabled cpumask_var_t Per-CPU 中断使能掩码
pending_mask cpumask_var_t 待处理 CPU 掩码(用于亲和性)
kstat_irqs struct irqstat * 中断统计信息

2.2.2 中断号映射流程

  1. 硬件中断号(hwirq):由 GIC 或设备树指定,如 SPI 32 对应 hwirq 32。
  2. Linux 中断号(irq):通过 irq_domain 将 hwirq 映射到全局 irq 号。
  3. irq_desc 查找:内核通过 irq_to_desc(irq) 获取对应的 irq_desc。
  4. action 链表遍历:中断触发时,遍历 irq_desc->action 链表,执行注册的 handler。
关键函数:request_irq(irq, handler, flags, name, dev) 内部会分配 irqaction,挂入 irq_desc->action 链表,并调用 irq_chip->irq_startup 使能中断。

2.3 中断处理流程概述

Linux 中断处理分为上半部(硬中断上下文)和下半部(软中断上下文)。上半部要求快速完成,下半部处理耗时任务。

2.3.1 完整处理流程

流程速记:硬件触发 → GIC 分发 → CPU 异常向量 → 内核 entry → 通用中断处理 → 调用 handler → 标记软中断 → 退出异常 → 执行软中断/tasklet。
  1. 硬件阶段:外设拉高中断线,GIC 仲裁后向指定 CPU 发送 IRQ 信号。
  2. 异常向量:CPU 跳转到 vectors 表中的 IRQ 入口(如 el1_irq)。
  3. 内核 entry:保存上下文,调用 handle_arch_irq(GIC 注册的中断处理入口)。
  4. GIC 处理:gic_handle_irq 读取 GICC_IAR 获取中断号,调用 handle_domain_irq。
  5. 通用中断层:__handle_domain_irq 查找 irq_desc,调用 irq_desc->handle_irq(如 handle_fasteoi_irq)。
  6. 执行 handler:遍历 action 链表,执行驱动注册的 irq_handler_t。
  7. 标记软中断:handler 返回 IRQ_WAKE_THREAD 或通过 tasklet_schedule 触发下半部。
  8. 退出异常:恢复上下文,返回被中断的进程。
  9. 下半部执行:在 do_softirq 或 ksoftirqd 中执行 tasklet/softirq。

2.3.2 上下半部分工对比

特性 上半部(硬中断) 下半部(软中断/tasklet)
执行上下文 中断上下文(不可睡眠) 软中断上下文(不可睡眠)
中断屏蔽 本地 CPU 中断关闭 不屏蔽硬件中断
执行时间 极短(μs 级) 可稍长(ms 级)
典型操作 清除中断标志、拷贝数据、唤醒 协议处理、数据解析、唤醒等待队列
调度方式 硬件触发 软件触发(tasklet_schedule)
并发性 串行(同一中断号) 可并行(不同 CPU)

2.4 中断子系统架构总览(SVG)

下图展示了从外设触发中断到下半部执行的完整架构,包含 GIC、irq_desc、上下半部处理路径。

Linux 中断子系统架构总览 Linux 中断子系统架构总览 外设 GIC 中断控制器 CPU 异常向量 内核 entry 通用中断处理 irq_desc 查找 上半部 handler 下半部调度 tasklet 执行 ksoftirqd 返回进程上下文 说明 上半部:快速响应,不可睡眠 下半部:延迟处理,可调度

2.5 关键数据结构关系(SVG)

中断子系统的核心数据结构包括 irq_desc、irq_data、irq_chip、irqaction 和 irq_domain。下图展示了它们之间的引用关系。

中断子系统核心数据结构关系 中断子系统核心数据结构关系 struct irq_desc 中断描述符(核心) struct irq_data 硬件中断数据 struct irq_chip 中断控制器操作函数 struct irqaction 中断处理函数链表 struct irq_domain hwirq → irq 映射 包含 包含 引用 引用

2.6 中断处理流程代码示例

以下代码展示了一个典型的中断驱动注册流程,包含上半部 handler 和 tasklet 下半部。

#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>

#define GPIO_IRQ_NUM  32   /* 假设 SPI 32 号中断 */

static struct tasklet_struct my_tasklet;

/* 下半部 tasklet 处理函数 */
static void my_tasklet_handler(unsigned long data)
{
    printk(KERN_INFO "tasklet: 处理中断下半部, data=%lu\n", data);
    /* 执行耗时操作:数据解析、协议处理等 */
}

/* 上半部中断处理函数 */
static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
    printk(KERN_INFO "上半部: 中断 %d 触发\n", irq);
    
    /* 清除硬件中断标志(由芯片相关操作完成) */
    
    /* 调度 tasklet 执行下半部 */
    tasklet_schedule(&my_tasklet);
    
    return IRQ_HANDLED;
}

static int __init my_driver_init(void)
{
    int ret;
    
    /* 初始化 tasklet */
    tasklet_init(&my_tasklet, my_tasklet_handler, 0);
    
    /* 注册中断 */
    ret = request_irq(GPIO_IRQ_NUM, my_irq_handler,
                      IRQF_TRIGGER_RISING, "my_device", NULL);
    if (ret) {
        printk(KERN_ERR "request_irq failed: %d\n", ret);
        return ret;
    }
    
    printk(KERN_INFO "驱动加载成功,中断 %d 已注册\n", GPIO_IRQ_NUM);
    return 0;
}

static void __exit my_driver_exit(void)
{
    /* 注销中断 */
    free_irq(GPIO_IRQ_NUM, NULL);
    
    /* 销毁 tasklet */
    tasklet_kill(&my_tasklet);
    
    printk(KERN_INFO "驱动卸载完成\n");
}

module_init(my_driver_init);
module_exit(my_driver_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Linux 驱动专家");
MODULE_DESCRIPTION("中断处理与 tasklet 示例");
代码要点:
  • request_irq 注册上半部 handler,在中断上下文中执行。
  • tasklet_schedule 在 handler 中调用,触发下半部。
  • tasklet_init 在模块初始化时创建 tasklet,tasklet_kill 在卸载时销毁。
  • 上半部应尽量短,只做必要操作(如清除标志、调度下半部)。

2.7 本章小结

知识点 核心内容 工程应用
GIC 中断控制器 SGI/PPI/SPI/LPI 类型,GICv2/v3/v4 差异 设备树配置中断类型与触发方式
irq_desc 描述符 连接硬件中断号与软件 handler 通过 irq_to_desc 调试中断状态
上下半部分离 上半部快速响应,下半部延迟处理 tasklet 用于协议解析、数据拷贝
处理流程 硬件触发 → GIC → CPU → handler → tasklet 遵循流程编写高效中断驱动