2、RTOS中断管理机制:中断服务例程(ISR)与任务的关系、临界区保护、中断延迟
好,咱们接着聊中断。上一章我们把中断的硬件基础讲清楚了,这一章要深入RTOS的核心——中断服务例程(ISR)和任务到底怎么配合?临界区怎么保护?中断延迟这个指标又该怎么看?
说实话,我见过不少工程师,裸机编程玩得很溜,一上RTOS就翻车。翻车点往往就在中断管理上。你想想看,裸机里中断就是中断,主循环就是主循环,关系简单。但RTOS里多了任务调度,中断和任务之间就有了“互动”,这个互动一旦处理不好,系统就崩了。
2.1 ISR与任务的关系:谁该做什么?
先问一个问题:中断来了,数据到了,你是在ISR里直接处理,还是扔给任务去处理?
我的答案是:ISR只做最紧急、最轻量的事。什么叫最紧急?把硬件数据搬到内存缓冲区,清中断标志位,通知任务“活来了”。这就够了。剩下的解析、运算、协议栈处理,统统交给任务。
为什么?因为ISR里不能阻塞,不能等信号量,不能调用可能引起任务切换的API(除非你明确知道自己在做什么)。ISR执行时间越长,系统响应越差。我在项目中遇到过有人把整个TCP/IP协议栈解析放在ISR里,结果系统中断延迟飙到毫秒级,CAN报文直接丢帧。嗯,那场面,相当惨烈。
所以,ISR和任务的分工,我建议这样划分:
| 职责 | ISR | 任务 |
|---|---|---|
| 数据搬运 | ✅ 从硬件FIFO搬到内存缓冲区 | ❌ 太慢,来不及 |
| 清中断标志 | ✅ 必须做 | ❌ 硬件层面的事 |
| 协议解析 | ❌ 耗时太长 | ✅ 慢慢来 |
| 复杂计算 | ❌ 阻塞调度 | ✅ 适合 |
| 通知任务 | ✅ 发信号量或消息 | ❌ 被动等待 |
说白了,ISR是“快递员”,只负责把包裹放到门口,任务才是“拆包裹的人”。
2.2 临界区保护:别让中断把你的数据撕碎
临界区,这是个老生常谈的话题。但我要说的是,在RTOS里,临界区保护比裸机复杂得多。
裸机里,你关中断就完事了。RTOS里呢?你不仅要考虑中断,还要考虑其他任务。比如一个全局变量,ISR在写,任务在读。如果不保护,任务读到一半,ISR把数据改了,那读出来的就是“半成品”,逻辑直接崩。
我个人的习惯是:能不用全局变量就不用。但有些场景躲不开,比如共享缓冲区、状态标志。这时候就得用临界区保护。
RTOS里常见的临界区保护方式有两种:
- 关中断:最简单粗暴,但会影响中断延迟。适合保护极短的操作,比如修改一个变量。
- 使用互斥量或信号量:更优雅,但ISR里不能等信号量(会死锁)。所以ISR和任务之间,我建议用任务通知或无锁队列。
来看一个典型例子。假设一个ADC中断,每次采样完把数据放到缓冲区:
// 错误示范:没有保护
int buffer[100];
int index = 0;
void ADC_ISR(void) {
buffer[index++] = read_adc(); // 如果此时任务也在读buffer,数据就乱了
}
// 正确示范:关中断保护
void ADC_ISR(void) {
taskENTER_CRITICAL();
buffer[index++] = read_adc();
taskEXIT_CRITICAL();
}
注意,taskENTER_CRITICAL() 在FreeRTOS里会关掉所有可屏蔽中断。所以临界区里的代码一定要短,否则中断延迟会爆炸。我曾经在一个项目里看到有人把日志打印放在临界区里,结果串口打印一次要几毫秒,中断延迟直接上天。嗯,后来我帮他改成了用DMA+环形缓冲区,问题才解决。
vTaskDelay()、printf()(如果用了重定向)、或者等待信号量。否则系统会死锁,或者中断延迟变得不可接受。
2.3 中断延迟:一个被低估的指标
中断延迟,说白了就是从中断触发到ISR第一条指令执行的时间。这个指标在车载领域极其重要。为什么?因为CAN报文、LIN总线、传感器数据都有严格的时序要求。如果中断延迟太大,数据就丢了。
中断延迟由哪些部分组成?我列一下:
- 硬件延迟:CPU响应中断的时间,一般几个时钟周期,固定不变。
- 中断嵌套:如果高优先级中断正在执行,低优先级中断就得等。
- 临界区:这是最大的变量。如果临界区太长,所有中断都得排队。
- 指令执行:当前指令执行完才能响应中断,一般忽略不计。
你看,真正可控的是第2点和第3点。尤其是临界区,很多工程师不注意,随手写个长临界区,结果系统响应变慢。
我建议你这样做:
- 测量中断延迟:用GPIO翻转法,在ISR入口拉高一个引脚,用示波器看。这是最直观的方法。
- 设置中断优先级:把时间敏感的中断(比如CAN、定时器)设成最高优先级,确保它们不会被其他中断阻塞。
- 缩短临界区:临界区里只做原子操作,比如赋值、读寄存器。复杂逻辑放到外面。
最后说一句,中断延迟不是越小越好,而是可预测才好。车载系统要求确定性,你哪怕延迟大一点,但只要每次都是固定的,系统就能接受。最怕的是忽大忽小,那才是噩梦。
好了,这一章就到这里。下一章我们讲中断嵌套和优先级配置,那是另一个容易踩坑的地方。到时候我会分享一个我踩过的坑——因为优先级设错了,导致CAN报文在高速工况下周期性丢帧。嗯,那故事挺有意思的。