1. 中断基础概念:什么是中断、中断向量表、中断处理流程

中断(Interrupt)是嵌入式 Linux 系统中最重要的异步事件处理机制。它允许硬件设备在需要 CPU 关注时,主动打断当前正在执行的程序,转而执行一个预先定义好的处理函数。理解中断的基础概念,是掌握 tasklet 和工作队列的前提。

1.1 什么是中断

中断是一种硬件或软件触发的信号,用于通知处理器发生了需要立即处理的事件。与轮询(Polling)方式相比,中断机制大幅提高了 CPU 的利用率——CPU 不需要一直检查设备状态,只有在事件发生时才会被通知。

特性 中断方式 轮询方式
CPU 利用率 高,无事时 CPU 可执行其他任务 低,CPU 需不断检查状态
响应延迟 低,事件发生后立即响应 取决于轮询间隔
实现复杂度 较高,需配置中断控制器和向量表 简单,仅需循环读取寄存器
适用场景 低频或突发性事件(按键、网络包到达) 高频且可预测的事件(传感器数据采集)
关键概念:中断上下文(Interrupt Context)——当 CPU 执行中断服务程序(ISR)时,它运行在中断上下文中。在此上下文中,不能调用可能引起睡眠的函数(如 copy_from_user()、kmalloc(GFP_KERNEL)),因为中断没有进程调度实体。

1.2 中断向量表

中断向量表(Interrupt Vector Table, IVT)是存储在内存中的一张表格,每个表项对应一个中断号,存放着该中断对应的处理函数入口地址。当 CPU 收到中断信号时,硬件自动根据中断号查找向量表,跳转到对应的处理函数。

在 ARM Cortex-A 系列处理器中,中断向量表通常存放在地址 0x00000000 或 0xFFFF0000(由系统配置决定)。在 x86 架构中,中断向量表称为 IDT(Interrupt Descriptor Table)。

中断向量表结构示意图 中断向量表 向量 0:复位 向量 1:未定义指令 向量 2:软件中断 (SWI) 向量 3:预取中止 向量 4:数据中止 CPU 根据中断号查找表项 获取处理函数入口地址 跳转执行 ISR 函数 每个中断向量占用 4 字节(32 位)或 8 字节(64 位),存放跳转指令或函数地址

1.3 中断处理流程

一次完整的中断处理流程可以分为三个阶段:中断响应、中断服务程序执行、中断返回。在 Linux 内核中,中断处理被进一步拆分为上半部(Top Half)和下半部(Bottom Half),以满足实时性和系统吞吐量的要求。

1.3.1 硬件阶段

  1. 中断信号产生:外设(如网卡、定时器)将中断请求线(IRQ)拉高或拉低。
  2. 中断控制器仲裁:GIC(Generic Interrupt Controller)或 APIC 根据优先级选择当前最高优先级的中断。
  3. CPU 保存现场:硬件自动将当前程序计数器(PC)、状态寄存器(CPSR/SPSR)压入栈中。
  4. 查表跳转:CPU 根据中断号从向量表中取出入口地址,跳转到中断服务程序。

1.3.2 软件阶段(Linux 内核)

  1. 上半部(Top Half):执行时间敏感的操作,如清除中断标志、拷贝硬件寄存器数据到内存缓冲区。上半部在中断上下文中运行,必须快速完成。
  2. 下半部调度:上半部通过 tasklet_schedule()、workqueue 或软中断机制,将耗时工作推迟到下半部执行。
  3. 下半部执行:在更宽松的上下文中(软中断、tasklet 或进程上下文)完成数据处理、协议解析等任务。
Linux 中断处理完整流程 硬件设备 (网卡/定时器) 中断控制器 (GIC/APIC) CPU 内核 (保存现场) 中断请求 IRQ 信号 上半部(Top Half) • 清除中断标志 • 拷贝硬件寄存器数据 下半部调度 • tasklet_schedule() • workqueue 调度 下半部执行 • 数据处理 • 协议解析 中断返回 恢复现场 → 返回被中断程序 无下半部时直接返回 中断处理关键原则 1. 上半部必须快速完成(通常微秒级),不能调用可能导致睡眠的函数 2. 下半部可以执行更复杂的操作,但也要避免长时间阻塞 3. 中断处理期间,同级别或更低级别的中断会被屏蔽

1.4 中断上下文与进程上下文的区别

特性 中断上下文 进程上下文
可睡眠 否 是
可访问用户空间 否 是
调度器状态 不可调度 可被调度
栈空间 有限(通常 4KB~8KB) 较大(通常 8KB+)
典型函数限制 不能使用 mutex_lock()、copy_from_user() 无特殊限制
常见错误:初学者容易在中断服务程序中调用 printk() 过多导致系统卡死,或使用 kmalloc(GFP_KERNEL) 导致内核崩溃。在中断上下文中,应使用 GFP_ATOMIC 标志分配内存。

1.5 中断号与 IRQ 号

每个中断源在系统中都有一个唯一的中断号(IRQ number)。在设备树(Device Tree)中,中断控制器通过 interrupts 属性指定设备使用的中断号。Linux 内核通过 request_irq() 函数注册中断处理程序:

// 注册中断处理函数原型
int request_irq(unsigned int irq,
                irq_handler_t handler,
                unsigned long flags,
                const char *name,
                void *dev_id);

// 示例:注册 GPIO 按键中断
static irqreturn_t button_isr(int irq, void *dev_id) {
    // 上半部处理:记录按键事件
    // 调度下半部(tasklet 或 workqueue)
    return IRQ_HANDLED;
}

// 在驱动初始化中调用
if (request_irq(gpio_to_irq(KEY_GPIO), button_isr,
                IRQF_TRIGGER_FALLING, "my_button", NULL)) {
    pr_err("Failed to register IRQ\n");
    return -EBUSY;
}
实践提示:在嵌入式系统中,使用 gpio_to_irq() 函数将 GPIO 引脚号转换为对应的 IRQ 号。中断标志 IRQF_TRIGGER_FALLING 表示下降沿触发,IRQF_TRIGGER_RISING 表示上升沿触发,IRQF_SHARED 表示多个设备共享同一中断线。

1.6 本章小结

  • 中断是硬件异步通知 CPU 事件发生的机制,比轮询更高效。
  • 中断向量表是中断号到处理函数入口地址的映射表,CPU 通过它快速定位 ISR。
  • 中断处理流程分为硬件响应、上半部执行、下半部调度与执行、中断返回四个阶段。
  • 中断上下文有严格的编程限制,不能睡眠、不能访问用户空间、栈空间有限。
  • 理解这些基础概念后,下一章将深入探讨 tasklet 的实现机制与编程接口。