1. 中断处理基础
1.1 中断概念与分类
中断(Interrupt)是嵌入式系统中CPU与外部设备异步通信的核心机制。当外设需要CPU关注时,通过硬件信号线向CPU发送中断请求,CPU暂停当前任务,转去执行中断服务程序(ISR),处理完成后返回断点继续执行。
核心要点:中断避免了CPU轮询外设状态带来的性能浪费,是实现低延迟、高吞吐驱动的基础。
| 分类维度 | 类型 | 说明 |
|---|---|---|
| 触发方式 | 电平触发 / 边沿触发 | 电平触发持续有效直到被清除;边沿触发仅在信号跳变时产生 |
| 硬件来源 | IRQ / FIQ | ARM架构中FIQ优先级更高,有独立寄存器组 |
| 软件角度 | 硬中断 / 软中断 | 硬中断由外设硬件产生;软中断由软件指令(如SWI)触发 |
| 可屏蔽性 | 可屏蔽中断 / 不可屏蔽中断(NMI) | 可屏蔽中断可通过CPU标志位关闭;NMI用于紧急事件 |
1.2 中断向量表
中断向量表(Interrupt Vector Table, IVT)是存储在固定内存地址的跳转表,每个中断源对应一个表项,存放该中断的服务程序入口地址。CPU响应中断后,自动根据中断号从向量表中取出地址并跳转执行。
| 架构 | 向量表基址 | 表项大小 | 特点 |
|---|---|---|---|
| ARM Cortex-M | 0x00000000(可重定位) | 4字节/项 | 向量表包含堆栈指针初始值,支持中断嵌套 |
| ARM Cortex-A | 0xFFFF0000 / 0x00000000 | 4字节/项 | 通过CP15协处理器配置向量基址 |
| x86 | 实模式 0x00000;保护模式 IDTR | 8字节/项 | 中断描述符表(IDT)支持任务门、中断门、陷阱门 |
| RISC-V | mtvec / stvec 寄存器指定 | 4字节/项 | 支持直接模式和向量模式 |
1.3 中断上下文
中断上下文(Interrupt Context)是指CPU在执行ISR期间所处的特殊运行环境。与进程上下文不同,中断上下文不可睡眠、不可调度、不可访问用户空间。
| 对比项 | 进程上下文 | 中断上下文 |
|---|---|---|
| 可睡眠 | 是(通过schedule()) | 否(内核会触发BUG) |
| 可被抢占 | 是 | 否(除非中断嵌套) |
| 访问用户空间 | 是 | 否(current指针不可靠) |
| 持有锁 | 普通锁 / 互斥锁 | 仅限 spinlock(自旋锁) |
| 栈空间 | 进程内核栈(通常8KB) | 中断栈(独立或借用内核栈) |
| in_interrupt() | 返回0 | 返回非0 |
常见陷阱:在中断上下文中调用
kmalloc(GFP_KERNEL) 会导致睡眠,应使用 GFP_ATOMIC 标志。同样,mutex_lock() 在中断上下文中绝对禁止使用。
1.4 中断处理流程概述
一个完整的中断处理流程从硬件信号产生到ISR执行完毕,通常经历以下阶段:
- 外设触发:外设硬件将中断请求信号拉高(电平触发)或产生跳变(边沿触发)。
- 中断控制器仲裁:GIC(Generic Interrupt Controller)或APIC等中断控制器对多个中断源进行优先级裁决,向CPU发送IRQ信号。
- CPU保存现场:CPU自动保存当前程序状态寄存器(CPSR)、返回地址(LR)到对应模式栈,并切换到中断模式。
- 向量表查表:CPU根据中断号从向量表中读取ISR入口地址,跳转执行。
- ISR执行:执行中断服务程序,完成必要的硬件操作(如读取数据、清除中断标志)。
- 恢复现场与返回:执行中断返回指令(如ARM的MOVS PC, LR),恢复CPU状态,继续执行被中断的任务。
1.5 Linux内核中断处理机制
Linux内核将中断处理分为上半部(Top Half)和下半部(Bottom Half)两部分,以平衡实时性与吞吐量:
| 特性 | 上半部(硬中断) | 下半部(软中断 / Tasklet / Workqueue) |
|---|---|---|
| 执行时机 | 中断触发后立即执行 | 硬中断结束后,在安全时机执行 |
| 中断使能 | 默认关闭本地中断 | 中断已使能,可被抢占 |
| 执行时间 | 必须极短(微秒级) | 可稍长(毫秒级) |
| 典型操作 | 清除中断标志、拷贝硬件寄存器 | 协议解析、数据分发、唤醒等待队列 |
| API注册 | request_irq() |
tasklet_init() / INIT_WORK() |
驱动调优建议:对于高频中断(如网络收包、DMA完成),尽量将非紧急处理下沉到下半部,上半部只做最小必要操作。使用
IRQF_TIMER 标志可告知内核该中断为定时器类型,获得特殊调度优化。
1.6 中断处理中的关键数据结构
Linux内核使用 irq_desc 结构体管理每个中断号的状态与回调函数,驱动开发者通过 request_irq() 注册中断处理函数:
// 注册中断处理函数原型
int request_irq(unsigned int irq,
irq_handler_t handler,
unsigned long flags,
const char *name,
void *dev_id);
// 中断处理函数原型
irqreturn_t handler(int irq, void *dev_id);
// 返回值宏
#define IRQ_NONE (0) // 未处理
#define IRQ_HANDLED (1) // 已处理
#define IRQ_WAKE_THREAD (2) // 唤醒中断线程
参数调优要点:
flags中的IRQF_TRIGGER_RISING/FALLING/HIGH/LOW必须与硬件电气特性匹配,否则导致中断丢失或误触发。IRQF_SHARED标志允许多个驱动共享同一中断线,但所有共享驱动必须支持该模式。dev_id在共享中断中用于区分设备,必须唯一且非NULL。
1.7 中断延迟与性能指标
中断延迟(Interrupt Latency)是衡量系统实时性的关键指标,直接影响驱动性能:
| 指标 | 定义 | 典型值(嵌入式Linux) | 影响因素 |
|---|---|---|---|
| 中断响应延迟 | 从硬件触发到ISR第一条指令执行 | 1~10 |