1. 中断基础概念:中断定义、中断向量、中断号、中断上下文
1.1 中断定义:CPU 的异步事件响应机制
中断(Interrupt)是一种硬件或软件发出的异步信号,用于通知 CPU 发生了需要立即处理的事件。CPU 暂停当前正在执行的程序,保存现场,跳转到预先注册的中断服务程序(ISR)执行,处理完成后恢复现场并继续执行原程序。
核心特征:中断是异步的、优先级可配置的、由事件驱动的。与轮询(Polling)相比,中断能显著降低 CPU 占用率,提高系统实时响应能力。
类比理解:中断就像你在写代码时,电话铃响了(中断信号),你保存当前编辑的内容(保存现场),接电话(执行 ISR),挂断后继续写代码(恢复现场)。
1.2 中断向量与中断号:硬件到软件的映射桥梁
中断向量(Interrupt Vector)是中断服务程序入口地址的索引表。每个中断源对应一个唯一的中断向量号,CPU 通过中断向量号在向量表中查找对应的 ISR 地址。
中断号(IRQ Number)是 Linux 内核为每个中断源分配的软件标识符,用于驱动注册和中断管理。中断号与中断向量之间存在映射关系,但中断号是软件抽象层,中断向量是硬件层概念。
| 概念 | 层级 | 作用 | 示例 |
|---|---|---|---|
| 中断向量 | 硬件层 | CPU 通过向量号查找 ISR 入口地址 | x86 的 IDT(中断描述符表) |
| 中断号 | 软件层 | 内核用于标识中断源,驱动注册时使用 | request_irq(irq, handler, ...) |
| 中断线 | 物理层 | 硬件引脚或 PCI 中断线 | GPIO 中断线、PCI INTx |
1.3 中断上下文:驱动开发的核心约束
中断上下文(Interrupt Context)是指 CPU 在执行中断服务程序时所处的特殊运行环境。与进程上下文不同,中断上下文具有以下关键特征:
- 不可睡眠:中断上下文不在任何进程的调度范围内,不能调用可能导致睡眠的函数(如
kmalloc(GFP_KERNEL)、mutex_lock())。 - 不可抢占:在 Linux 内核中,中断上下文不能被其他进程抢占,但可以被更高优先级的中断嵌套。
- 栈空间有限:中断使用独立的栈(通常为 4KB~16KB),必须避免深度递归或大局部变量。
- 原子性要求:中断上下文中对共享资源的访问必须使用自旋锁等原子操作,不能使用信号量。
| 对比维度 | 进程上下文 | 中断上下文 |
|---|---|---|
| 可睡眠 | 是(可调用 schedule()) | 否(禁止睡眠) |
| 可抢占 | 是(内核态可被抢占) | 否(不可被进程抢占) |
| 锁机制 | 互斥锁、信号量、自旋锁 | 仅自旋锁(不能使用信号量) |
| 栈大小 | 进程栈(通常 8KB+) | 中断栈(通常 4KB~16KB) |
| 典型函数 | copy_from_user()、msleep() |
writel()、spin_lock() |
常见错误:在中断上下文中调用
kmalloc(GFP_KERNEL) 会导致内核崩溃,因为 GFP_KERNEL 可能触发内存回收而睡眠。应使用 GFP_ATOMIC 标志。
1.4 中断处理流程:从硬件触发到软件响应
一个完整的中断处理流程包含以下步骤:
- 硬件触发:外设产生中断信号,通过中断线发送给中断控制器。
- 中断仲裁:中断控制器根据优先级选择最高优先级的中断,向 CPU 发送中断请求。
- CPU 响应:CPU 完成当前指令后,保存程序计数器(PC)和状态寄存器,进入中断模式。
- 向量查找:CPU 根据中断向量号在向量表中查找 ISR 入口地址。
- 执行 ISR:CPU 跳转到 ISR 执行,处理中断事件。
- 中断返回:ISR 执行完毕,CPU 恢复现场,继续执行被中断的程序。
1.5 中断分类:同步与异步、硬中断与软中断
中断按触发方式分为同步中断和异步中断;按处理机制分为硬中断和软中断。
| 分类 | 触发方式 | 典型场景 | Linux 对应机制 |
|---|---|---|---|
| 同步中断 | CPU 指令执行直接导致 | 除零异常、缺页异常、系统调用 | 异常处理(trap) |
| 异步中断 | 外部硬件事件触发 | 网卡收包、按键按下、定时器到期 | IRQ 中断 |
| 硬中断 | 硬件信号直接触发 | 外设中断、时钟中断 | request_irq() 注册 |
| 软中断 | 软件通过指令触发 | 网络协议栈处理、任务调度 | softirq、tasklet |
驱动开发要点:硬中断 ISR 必须快速完成(通常微秒级),将耗时处理下放到软中断或工作队列中执行。Linux 内核提供
top half(硬中断)和 bottom half(软中断/tasklet/工作队列)的分层机制。
1.6 中断号分配与动态申请
在 Linux 内核中,中断号可以通过以下方式获取:
- 静态分配:传统 ISA 设备使用固定的中断号(如 IRQ 0 为时钟中断)。
- 动态分配:PCI/平台设备通过
platform_get_irq()或pci_irq_vector()动态获取中断号。 - 设备树绑定:嵌入式设备通过设备树(Device Tree)的
interrupts属性指定中断号。
/* 动态获取中断号示例 */
int irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "failed to get IRQ\n");
return irq;
}
/* 注册中断处理函数 */
ret = request_irq(irq, my_isr, IRQF_TRIGGER_RISING,
"my_device", dev_id);
if (ret) {
dev_err(&pdev->dev, "failed to request IRQ %d\n", irq);
return ret;
}
最佳实践:始终使用动态中断号分配,避免硬编码。在设备树中明确指定中断触发类型(上升沿、下降沿、电平触发),确保与硬件设计一致。