【第23期】资源保护:关中断 vs 互斥量 (Mutex)

核心差异 :关中断是"让世界停止";互斥量是"排队等待"。而互斥量特有的 优先级继承 机制,是它区别于普通信号量的关键。

1. 裸机锁机制:关中断 (The Sledgehammer)

这是最暴力的保护方式。

// 裸机保护 I2C

__disable_irq(); // 1. 所有人闭嘴,都不许动

I2C_Write(Address, Data); // 2. 操作资源

__enable_irq(); // 3. 恢复运行

  • 优点:速度极快,逻辑简单,绝对安全。

  • 缺点实时性杀手。如果你通过 I2C 发送 100 字节数据需要 5ms,那你关中断这 5ms 内,系统就像死了一样。电机控制环路会断开,高优先级中断会丢失。

在 RTOS 中,除非是极其短小的临界区(几行代码),否则严禁使用关中断来保护耗时资源

2. RTOS 锁机制:互斥量 (Mutex)

互斥量(Mutual Exclusion)就像一把钥匙。

  • Take (拿钥匙):任务 A 想用 I2C,先申请锁。如果锁没人用,拿走,进屋,锁门。

  • Block (排队) :任务 B 也想用 I2C,发现锁没了。RTOS 让任务 B 睡觉(阻塞),不占 CPU,直到任务 A 归还钥匙。

  • Give (还钥匙):任务 A 用完了,交出钥匙。RTOS 叫醒任务 B,把钥匙给它。

// 任务 A

xSemaphoreTake(xMutexI2C, portMAX_DELAY); // 申请锁

I2C_Write(Address, Data); // 慢速操作

xSemaphoreGive(xMutexI2C); // 释放锁

这就解决了实时性问题:当任务 A 在慢慢发 I2C 时,中断依然可以响应,其他不涉及 I2C 的高优先级任务也能抢占执行。


3. 恐怖的陷阱:优先级翻转 (Priority Inversion)

既然 Mutex 这么好,为什么还需要特别设计?为什么不能用普通的二值信号量(Binary Semaphore)代替?

因为普通的排队机制会导致 "中等优先级的任务卡死高优先级任务" 的荒谬现象。

剧本推演: 有三个任务:High (高)Medium (中)Low (低) 。 只有 HighLow 需要共享 I2C 锁。

  1. Low 先运行,拿到了 I2C 锁,开始干活。

  2. High 突然醒来(比如 1ms 时间到了),抢占了 Low。

  3. High 也要用 I2C,发现锁在 Low 手里。High 没办法,只能进入阻塞,等 Low 还有完锁。

    • 此时状态:High 等 Low。这很正常。
  4. 关键时刻 :突然,那个和 I2C 八竿子打不着的 Medium 醒来了!

  5. 因为 Medium 的优先级 > Low。所以 Medium 抢占了 Low,开始疯狂执行自己的业务。

  6. 灾难发生

    • High 在等 Low 还锁。

    • Low 想还锁,但它被 Medium 压着不能运行。

    • 结果:High 实际上在等 Medium 执行完!

    • 荒谬 :Medium(中)明明比 High(高)优先级低,却间接阻塞了 High。如果 Medium 是个死循环,High 就被永久饿死了。

这就是 优先级翻转 。1997 年,美国的 "火星探路者号 (Mars Pathfinder)" 就因为这个问题,在火星上频繁重启,最后是工程师从地球远程修补了代码才救回来。


4. 如何破局:优先级继承 (Priority Inheritance)

为了解决这个问题,互斥量 (Mutex) 区别于 信号量 (Semaphore) 的最大特性诞生了:优先级继承

修正后的剧本:

  1. Low 拿到了锁。

  2. High 醒来,申请锁,被 Low 挡住。

  3. RTOS 介入 :它可以检测到 High 被 Low 挡住了。RTOS 瞬间把 Low 的优先级提升到和 High 一样高(甚至更高一点)。

    • 此时:Low 变成了 VIP。
  4. Medium 醒来,想抢占 Low?没门! 因为现在 Low 的优先级(临时提拔的)比 Medium 高。

  5. Low 继续运行,直到把 I2C 用完,释放锁

  6. 释放锁的一瞬间,Low 的优先级瞬间跌回原来的低水平

  7. High 拿到锁,继续运行。

  8. High 跑完了,才轮到 Medium 跑。

总结:优先级继承机制,强迫拿着锁的低优先级任务"快点干活,干完赶紧滚",从而避免被中等优先级的捣乱者卡住。


5. 递归锁 (Recursive Mutex)

还有一个小细节:如果同一个任务连续两次 Take 同一把锁,会怎么样?

  • 普通 Mutex :第一次 Take 成功;第二次 Take 时,发现锁被"别人"(其实是自己)拿着,于是自己等待自己------死锁 (Deadlock)

  • 递归 Mutex:允许同一个任务多次 Take。它内部有个计数器,Take 几次就要 Give 几次,直到计数器归零,锁才真正释放。

适用场景: 函数 A 拿了锁,调用函数 B。函数 B 为了安全,也拿同一把锁。这时必须用递归锁。


本章关键点Summary

  1. 关中断:适合极短的原子操作,不适合耗时外设。

  2. 优先级翻转:这是 RTOS 资源管理中最大的坑,中级任务可能会间接卡死高级任务。

  3. 互斥量 (Mutex) :通过 优先级继承 完美解决了翻转问题。

  4. 原则 :在 RTOS 中,保护共享资源(外设、内存块),请认准 Mutex,不要用普通的 Semaphore。

/***************************************************

* 本文为作者《嵌入式开发基础与工程实践》系列文章之一。

* 关注即可订阅后续内容更新,翻阅往期信息,采用异步推送机制,无需主动轮询。

* 转发本文可视为一次网络广播,有助于更多节点接收该信息。

***************************************************/

相关推荐
￴ㅤ￴￴ㅤ9527超级帅2 小时前
3、stm32的复位和时钟控制RCC
stm32·单片机·嵌入式硬件
Mcband2 小时前
Java 三方 JSON 比对
java·开发语言·json
世转神风-2 小时前
qt-通信协议基础-uint64_t转QByteArray-小端系统
开发语言·qt
easyboot2 小时前
python获取C#WEBAPI的数据
开发语言·python·c#
梨落秋霜2 小时前
Python入门篇【字符串】
开发语言·python
superman超哥2 小时前
Rust 复合类型:元组与数组的内存布局与性能优化
开发语言·后端·性能优化·rust·内存布局·rust复合类型·元组与数组
雾岛听风眠2 小时前
MCU中的晶振
单片机·嵌入式硬件
up向上up2 小时前
基于STM32的多参数水温水质检测报警Proteus仿真设计
stm32·嵌入式硬件·proteus
ACP广源盛139246256732 小时前
GSV2125D@ACP#2125产品规格参数详解及产品应用分享
嵌入式硬件·计算机外设·音视频