1. 中断基础概念:中断的定义、中断类型、中断上下文、中断优先级与嵌套
各位做嵌入式开发的朋友,咱们今天聊聊中断。说实话,中断这玩意儿是内核里最基础也最容易出坑的地方。我刚开始做驱动时,就因为对中断上下文理解不到位,踩过不少坑。今天我把这些基础概念掰开揉碎了讲清楚。
1.1 中断到底是什么?
中断,说白了就是CPU正在干一件事,突然被外部或内部的一个信号打断,转去处理更紧急的事情。处理完再回来接着干原来的活。这就像你正在写代码,突然电话响了——你得先接电话,接完再继续写。
从硬件角度看,中断是外设向CPU发送的一个电信号。从软件角度看,它是内核响应异步事件的一种机制。我个人习惯把中断理解为「硬件主动通知CPU:我有事找你,你停一下手头的活」。
核心要点:中断是异步事件处理机制,它让CPU不用轮询等待外设状态,大大提高了系统效率。
1.2 中断类型:IRQ、FIQ、NMI
ARM架构下,中断主要分三种。我当年在移植Linux内核到某款ARM芯片时,就因为这三种中断的配置搞错过一次,导致系统频繁死机。咱们一个个说。
| 中断类型 | 全称 | 特点 | 典型用途 |
|---|---|---|---|
| IRQ | Interrupt Request | 普通中断,可被屏蔽 | 外设中断(网卡、串口等) |
| FIQ | Fast Interrupt Request | 快速中断,优先级高,有独立寄存器 | 高速数据传输、实时性要求高的场景 |
| NMI | Non-Maskable Interrupt | 不可屏蔽中断,优先级最高 | 看门狗、硬件故障、电源异常 |
IRQ 是最常见的中断类型。你写的驱动里,大部分外设中断都是IRQ。它可以通过CPU的CPSR寄存器中的I位来屏蔽。
FIQ 在ARM里是个特殊存在。它有自己的影子寄存器组,处理时不需要保存和恢复普通寄存器,所以速度更快。我记得在做一个高速数据采集项目时,就专门把ADC的中断配成了FIQ,才满足了采样率要求。
NMI 这玩意儿,嗯,你想想看,连CPU都屏蔽不了它。一旦触发,CPU必须立即响应。我在项目中用NMI处理过电源掉电保护——电压一掉,NMI立刻触发,CPU赶紧保存关键数据到Flash里。
避坑指南:我曾经在配置FIQ时,忘记把中断向量表里的FIQ入口地址设置正确,结果一触发FIQ就跳飞了。调试了两天才发现是链接脚本的问题。所以,用FIQ时一定要检查向量表配置。
1.3 中断上下文
这个概念特别重要。很多驱动bug都出在对中断上下文的理解上。什么是中断上下文?说白了就是CPU当前处于中断处理程序执行期间的状态。
中断上下文有几个关键特征:
- 不可睡眠:中断上下文里不能调用可能导致睡眠的函数,比如kmalloc(GFP_KERNEL)、mutex_lock()等。为什么?因为中断上下文没有进程调度实体,睡眠了没人能唤醒它。
- 不可调度:中断处理期间,CPU不会主动让出执行权给其他进程。
- 栈空间有限:每个CPU的中断栈通常只有几KB,别在中断里搞大数组或递归。
警告:我见过有人在中断处理函数里调用printk打印大量调试信息,结果栈溢出导致系统崩溃。printk本身没问题,但格式化字符串时可能会用栈空间。建议中断里只做最必要的事,复杂操作放到下半部(tasklet或workqueue)去做。
判断当前是否在中断上下文,可以用内核提供的API:
// 检查是否在中断上下文
if (in_interrupt()) {
// 当前在中断上下文,不能睡眠
// 只能用GFP_ATOMIC分配内存
data = kmalloc(size, GFP_ATOMIC);
} else {
// 进程上下文,可以睡眠
data = kmalloc(size, GFP_KERNEL);
}
1.4 中断优先级与嵌套
中断优先级,说白了就是给中断排个队。高优先级的中断可以打断低优先级的中断处理。这就是中断嵌套。
在ARM Cortex-A系列处理器中,中断优先级由GIC(Generic Interrupt Controller)管理。GIC支持最多256个优先级(实际芯片可能只实现部分)。
中断嵌套的规则:
- 高优先级中断可以打断低优先级中断的处理
- 同级中断不能互相打断
- 低优先级中断不能打断高优先级中断
我举个例子:假设系统里有三个中断,优先级分别是1(最高)、5、10(最低)。
- CPU正在处理优先级10的中断
- 此时优先级5的中断来了——它会打断优先级10的处理,CPU转去处理优先级5
- 在优先级5的处理过程中,优先级1的中断来了——它又会打断优先级5,CPU转去处理优先级1
- 优先级1处理完,回到优先级5继续处理
- 优先级5处理完,再回到优先级10继续处理
这就是中断嵌套的典型流程。但要注意,嵌套层数不能太多,否则栈空间会爆。我曾经在一个项目中,因为中断嵌套层数达到5层,导致中断栈溢出,系统随机死机。查了整整一周才定位到问题。
经验之谈:我个人建议,除非有严格的实时性要求,否则尽量少用中断嵌套。把中断处理做得短而快,复杂逻辑放到下半部去处理。这样既保证了实时性,又避免了嵌套带来的栈溢出风险。
1.5 本章知识体系
下面这张图展示了中断基础概念的核心逻辑关系,我画出来方便你理解:
好了,中断的基础概念就讲到这里。这些内容看似简单,但每个点背后都有不少坑。你写驱动时,多想想当前代码在什么上下文中执行,中断优先级怎么配,嵌套会不会出问题。把这些基础打牢了,后面移植中断子系统时才能游刃有余。
小建议:刚开始学中断时,可以在开发板上写个简单的中断驱动——比如按一下按键触发中断,在中断里点亮一个LED。亲手跑一遍,比看十遍书都管用。