第3章:关中断实现互斥——临界区保护与中断延迟影响分析

各位同学,咱们今天聊一个RTOS里最基础、也最“暴力”的互斥手段——关中断。

说实话,我刚入行那会儿,觉得这玩意儿太简单了。不就是把中断关了,干完活再打开嘛?后来在项目里被坑了几次,才明白这里面的门道有多深。嗯,今天我就把这些年踩过的坑、总结的经验,一次性讲清楚。

3.1 关中断为什么能实现互斥?

先想一个问题:临界区冲突的本质是什么?

说白了,就是两个执行流同时访问了同一个资源。在单核CPU上,这两个执行流要么是任务,要么是中断服务程序(ISR)。

那关中断为什么能解决问题?

因为中断是唯一能“抢走”CPU控制权的异步事件。你把中断一关,当前任务就独占CPU了。没有其他任务能打断你,ISR也进不来。这不就互斥了嘛?

核心原理:关中断 = 禁止所有可屏蔽中断 → 当前执行流独占CPU → 临界区安全

我在一个工业控制项目里遇到过这种情况:两个任务共享一个全局变量,用来记录传感器采样次数。不加保护的时候,偶尔会出现计数少一次的情况。查了半天,发现是任务A刚读完变量,还没来得及写回去,就被定时器中断打断了,中断里恰好也修改了这个变量。嗯,典型的“读-改-写”冲突。后来在操作前后加了关中断/开中断,问题就消失了。

3.2 关中断的典型用法

代码其实很简单,但细节决定成败。我给大家看一个标准模板:

// 保存当前中断状态,然后关中断
uint32_t saved_status = disable_interrupts();

// ---------- 临界区开始 ----------
// 这里操作共享资源
shared_counter++;
global_flags |= 0x01;
// ---------- 临界区结束 ----------

// 恢复之前的中断状态
restore_interrupts(saved_status);

注意看,我用了saved_status来保存状态。为什么?

因为调用这个函数的时候,中断可能本来就是关着的。如果你直接开中断,就会破坏调用者的状态。这在嵌套调用时特别容易出问题。我曾经见过一个同事,直接在临界区开头关中断,结尾开中断。结果在一个深层嵌套的调用链里,外层已经关了中断,内层一开,整个系统的时序全乱了。嗯,那天的调试过程,至今记忆犹新。

我的习惯:永远使用“保存-关-恢复”三步法。不要直接用“关-开”这种粗暴方式。这是RTOS编程的基本素养。

3.3 中断延迟——关中断的代价

关中断虽然简单粗暴,但代价也很明显:中断延迟

什么叫中断延迟?就是硬件产生中断信号,到CPU真正开始执行ISR之间的时间差。你关中断的时间越长,这个延迟就越大。

咱们用表格来看一下影响因素:

因素 影响程度 说明
临界区长度 直接正比 关中断时间越长,延迟越大
中断优先级 间接影响 高优先级中断要求更短的关中断时间
CPU架构 硬件相关 有些CPU关中断需要多条指令,本身就有延迟
嵌套深度 累积效应 多层嵌套关中断会叠加延迟

你想想看,如果一个系统里有关中断超过100微秒的临界区,而系统里有一个1kHz的定时器中断(周期1ms),那这个中断的抖动就会达到10%以上。对于电机控制、音频处理这类实时性要求高的场景,这简直是灾难。

避坑指南:我曾经在一个无人机飞控项目里,因为一个关中断的临界区写了200多行代码,导致GPS数据中断的延迟从5微秒飙升到80微秒。结果无人机在空中出现位置漂移,差点炸机。从那以后,我给自己定了个规矩:关中断的临界区,代码行数不超过20行,执行时间不超过10微秒。

3.4 关中断的适用场景

不是所有地方都适合关中断。我总结了一下,适合用关中断的场景有这几个:

  • 操作极其简单的共享变量:比如一个标志位、一个计数器,几条指令就能搞定
  • 中断ISR内部的临界区:中断里不能使用信号量(会阻塞),关中断是唯一选择
  • CPU架构不支持其他同步机制:比如一些低端MCU,没有原子指令
  • 对延迟不敏感的系统:比如温度采集,慢个几毫秒无所谓

不适合的场景:

  • 长时间操作:比如大数据拷贝、复杂计算
  • 调用可能阻塞的函数:关中断期间不能调用delay()、等待信号量等
  • 实时性要求极高的系统:比如电机FOC控制、音频流处理

3.5 如何测量关中断的影响?

我建议每个做RTOS开发的工程师,都养成一个习惯:量化你的关中断时间

方法很简单:

  1. 在关中断前,把一个GPIO拉高
  2. 在开中断后,把GPIO拉低
  3. 用示波器或者逻辑分析仪抓这个GPIO的波形
  4. 高电平的宽度就是关中断时间

我在项目里经常用这个方法做“地毯式排查”。把系统中所有关中断的地方都测一遍,然后列个表:

临界区位置 关中断时间(us) 触发频率 最大累积延迟 是否可优化
任务A:更新计数器 0.5 100Hz 0.5us
任务B:拷贝传感器数据 15.2 10Hz 15.2us 是,改用DMA
ISR:处理串口数据 3.8 1kHz 3.8us
任务C:日志输出 120.0 1Hz 120us 是,必须优化

看到任务C那个120微秒了吗?这就是典型的“定时炸弹”。虽然它只触发1Hz,但一旦触发,所有中断都要等120微秒。如果这时候有个紧急的中断进来,系统响应就慢了。

3.6 关中断 vs 其他互斥方式

咱们简单对比一下关中断和其他互斥手段:

互斥方式 延迟影响 适用场景 复杂度
关中断 全局延迟 极短临界区、ISR内部
信号量 无(但可能阻塞) 任务间同步、较长操作
互斥锁 无(但可能优先级反转) 任务间互斥、资源保护
原子操作 极低(硬件级) 简单变量操作

我个人习惯是:能用原子操作就不用关中断,能用信号量就不用关中断,关中断只留给最紧急、最简短的场景

3.7 实战建议

最后,给大家几条我在项目里总结出来的实战建议:

  • 关中断时间要量化:不要凭感觉,用示波器测一下
  • 临界区要短小精悍:只保护真正需要保护的代码,别把无关操作包进去
  • 注意嵌套:用保存/恢复的方式,不要直接开/关
  • ISR里只能用关中断:别在ISR里用信号量,会死锁
  • 定期审查:每个版本迭代后,重新检查所有关中断的地方

嗯,关中断这个话题,看似简单,但用好了是利器,用不好就是定时炸弹。希望大家在实际项目中,能把握好这个度。

下一章,咱们聊聊信号量的实现原理,那又是另一番天地了。