1. 中断基础概念

中断(Interrupt)是嵌入式系统与 Linux 内核驱动开发中最核心的异步事件处理机制。它允许硬件或软件在特定事件发生时,暂停当前正在执行的程序,转而执行一个优先级更高的中断服务程序(ISR),处理完成后恢复原程序的执行。理解中断的定义、分类、向量表与中断号,是后续进行中断申请与驱动移植的基石。

1.1 中断的定义与核心机制

中断的本质是一种硬件或软件触发的信号,用于通知处理器发生了需要立即关注的事件。与轮询(Polling)方式相比,中断机制能显著提高 CPU 利用率,避免 CPU 在等待事件时空转。

中断 vs 轮询 对比
特性中断驱动轮询方式
CPU 利用率高,无事时休眠或执行其他任务低,持续检查状态寄存器
响应延迟低,事件触发立即响应高,取决于轮询周期
实现复杂度较高,需配置中断控制器与向量表简单,仅需循环读取状态
适用场景低频或突发性事件(按键、网络包)高频可预测事件(ADC 连续采样)

中断处理流程通常包含以下步骤:

  1. 中断请求:外设或软件通过中断线向中断控制器(如 GIC、APIC)发送信号。
  2. 中断仲裁:中断控制器根据优先级决定是否向 CPU 发送 IRQ 信号。
  3. CPU 响应:CPU 保存当前上下文(PC、PSW 等),跳转到中断向量表对应入口。
  4. ISR 执行:执行中断服务程序,处理硬件事件。
  5. 中断返回:恢复上下文,继续执行被中断的程序。
中断处理流程示意图 外设/硬件 中断控制器 CPU 内核 中断向量表 ISR 执行 中断请求 IRQ 信号 查表跳转 说明: 中断控制器负责优先级仲裁 与向量映射,是中断管理的 核心硬件单元。

1.2 中断类型

根据触发源与实现方式,中断可分为以下三大类:

中断类型触发源特点典型示例
外部中断外部硬件设备(GPIO、定时器、DMA)异步、由硬件引脚电平或边沿触发按键按下、UART 接收数据
内部中断CPU 内部异常(除零、缺页、系统调用)同步、与指令执行直接相关除零异常、页错误、SWI 指令
软中断软件主动触发(系统调用、任务切换)同步、由程序指令发起Linux 系统调用(svc)、tasklet
驱动开发关注点: 在 Linux 设备驱动中,最常处理的是外部中断(通过 request_irq() 注册)。内部中断通常由内核异常处理框架接管,软中断则用于下半部(Bottom Half)机制,如 tasklet 和 workqueue。
中断类型分类结构图 中断类型 外部中断 内部中断 软中断 GPIO / 定时器 / DMA 除零 / 缺页 / 系统调用 tasklet / workqueue

1.3 中断向量表与中断号

中断向量表(Interrupt Vector Table, IVT) 是存储在固定内存地址的一张表,表中每一项对应一个中断号,保存着该中断对应的中断服务程序(ISR)入口地址。当 CPU 响应中断时,硬件自动根据中断号从向量表中取出地址并跳转执行。

中断号(IRQ Number) 是系统为每个中断源分配的整数标识符。在 Linux 内核中,中断号是驱动申请中断时使用的关键参数。

概念说明Linux 内核对应
硬件中断号由 SoC 或中断控制器定义的物理编号irq_desc[] 数组索引
软件中断号Linux 内核虚拟化后的逻辑中断号request_irq() 的第一个参数
中断向量CPU 架构相关的异常向量编号(如 ARM 的 IRQ 向量 0x18)arch/arm/kernel/entry-armv.S
常见误区: 中断号与 GPIO 编号不是同一概念。GPIO 中断需要先通过 gpio_to_irq() 将 GPIO 号转换为对应的 Linux 中断号,才能用于 request_irq()。直接使用 GPIO 编号注册中断会导致驱动加载失败。
中断向量表与中断号映射关系 中断源 Timer0 UART1 RX GPIO_A 按键 DMA 完成 I2C 事件 SPI 传输 中断号 (IRQ) IRQ 32 IRQ 33 IRQ 34 IRQ 35 IRQ 36 IRQ 37 向量表入口 0x8000_0100 0x8000_0104 0x8000_0108 0x8000_010C 0x8000_0110 0x8000_0114 映射关系 中断源 → 中断号 中断号 → 向量表 向量表 → ISR

1.4 中断号在 Linux 驱动中的使用

在 Linux 内核中,中断号是驱动与中断子系统交互的核心标识。以下是一个典型的中断申请代码片段,展示了中断号的使用方式:

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

/* 中断服务程序 */
static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
    /* 处理中断事件 */
    pr_info("Interrupt received on IRQ %d\n", irq);
    return IRQ_HANDLED;
}

static int __init my_driver_init(void)
{
    int irq_num;
    int gpio_pin = 42;  /* 假设 GPIO 42 */

    /* 将 GPIO 号转换为 Linux 中断号 */
    irq_num = gpio_to_irq(gpio_pin);
    if (irq_num < 0) {
        pr_err("Failed to map GPIO %d to IRQ\n", gpio_pin);
        return irq_num;
    }

    /* 申请中断 */
    if (request_irq(irq_num, my_irq_handler,
                    IRQF_TRIGGER_RISING,
                    "my_device", NULL)) {
        pr_err("Failed to request IRQ %d\n", irq_num);
        return -EBUSY;
    }

    pr_info("Driver loaded, IRQ %d registered\n", irq_num);
    return 0;
}

static void __exit my_driver_exit(void)
{
    int irq_num = gpio_to_irq(42);
    free_irq(irq_num, NULL);
    pr_info("Driver unloaded\n");
}

module_init(my_driver_init);
module_exit(my_driver_exit);
关键点总结:
  • 中断号是驱动申请中断的唯一凭证,必须通过平台设备、设备树或 gpio_to_irq() 获取。
  • 中断向量表由 CPU 架构和启动代码初始化,驱动开发者通常不需要直接操作向量表。
  • 外部中断、内部中断、软中断在 Linux 内核中有不同的处理路径,驱动主要关注外部中断的申请与释放。