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)。
1.3 中断处理流程
一次完整的中断处理流程可以分为三个阶段:中断响应、中断服务程序执行、中断返回。在 Linux 内核中,中断处理被进一步拆分为上半部(Top Half)和下半部(Bottom Half),以满足实时性和系统吞吐量的要求。
1.3.1 硬件阶段
- 中断信号产生:外设(如网卡、定时器)将中断请求线(IRQ)拉高或拉低。
- 中断控制器仲裁:GIC(Generic Interrupt Controller)或 APIC 根据优先级选择当前最高优先级的中断。
- CPU 保存现场:硬件自动将当前程序计数器(PC)、状态寄存器(CPSR/SPSR)压入栈中。
- 查表跳转:CPU 根据中断号从向量表中取出入口地址,跳转到中断服务程序。
1.3.2 软件阶段(Linux 内核)
- 上半部(Top Half):执行时间敏感的操作,如清除中断标志、拷贝硬件寄存器数据到内存缓冲区。上半部在中断上下文中运行,必须快速完成。
- 下半部调度:上半部通过
tasklet_schedule()、workqueue或软中断机制,将耗时工作推迟到下半部执行。 - 下半部执行:在更宽松的上下文中(软中断、tasklet 或进程上下文)完成数据处理、协议解析等任务。
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 的实现机制与编程接口。