摘要:两个任务共享一个串口,为了防止打印乱码,用了全局变量做标志位(Flag)或二值信号量(Binary Semaphore),结果系统卡死了?不是任务逻辑错了,而是 优先级反转(Priority Inversion)。本文解析为什么必须用 Mutex。
一、问题描述(现象)
**任务 A(高优先级)要打印 Log;
任务 B(低优先级)正在打印 Log;
任务 A 等待任务 B 释放资源,但任务 B 被任务 C(中优先级)抢占了;
结果:任务 A 等不到任务 B,系统卡死。**
很多工程师的直觉是:
加个 while(flag);自旋锁?
关中断(Disable IRQ)?
提高任务 B 的优先级?
二、原理分析
1. 物理模型
三个任务,三个优先级。
任务 A (High) -> 申请资源 -> 等待
任务 B (Low) -> 占用资源 -> 被抢占
任务 C (Middle)-> 运行 -> 不让 B 跑
2. 核心参数
-
Binary Semaphore(二值信号量):只是一个“钥匙”,不管谁拿着钥匙,都不影响优先级。
-
Mutex(互斥量):带有 优先级继承(Priority Inheritance) 机制的锁。
3. 反直觉真相
“用 Binary Semaphore 保护共享资源是错的。”
-
如果你用信号量:
-
低优先级任务 B 拿到信号量。
-
高优先级任务 A 申请信号量,失败,进入阻塞。
-
中优先级任务 C 抢占了 B。
-
结果:A 在等 B,B 在等 C,C 在跑。死锁。
-
三、工程级解决方案
方案 1:使用 Mutex 代替 Binary Semaphore(铁律)
凡是保护共享资源的,一律用 Mutex。
SemaphoreHandle_t xMutex;
void Task_Init(void)
{
xMutex = xSemaphoreCreateMutex();
}
void Task_A(void *arg)
{
if (xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE) {
// 访问共享资源(如 UART)
printf("Hello from Task A\\n");
xSemaphoreGive(xMutex);
}
}
方案 2:理解优先级继承
当你使用 Mutex 时,发生了什么:
任务 B(Low)拿到 Mutex。
任务 A(High)申请 Mutex。
系统自动把任务 B 的优先级提升到 High。
任务 B 运行完,释放 Mutex,变回 Low。
任务 A 获得 Mutex,运行。
结果:中优先级任务 C 再也没有机会插队打断 B,死锁解除。
方案 3:不要在中断里用 Mutex
Mutex 不能在 ISR 里使用。
-
ISR 里只能用 Binary Semaphore 或 Direct to Task Notification。
-
如果 ISR 也要访问共享资源,必须在 ISR 中释放信号量,由任务去拿 Mutex。
四、选型避坑建议
不要递归拿锁:
-
同一个任务连续两次 xSemaphoreTake(Mutex),会导致死锁(除非是 Recursive Mutex)。
持锁时间越短越好:
-
拿锁 -> 操作 -> 放锁,中间不要有 vTaskDelay。
死锁预防:
-
如果多个资源,规定获取锁的顺序(如先拿 A 再拿 B)。
五、总结 Checklist
-
[ ] 保护共享资源用的是 Mutex 还是 Binary Semaphore?
-
[ ] 是否理解 Mutex 的优先级继承机制?
-
[ ] 是否在 ISR 里错误地使用了 Mutex?
-
[ ] 持锁的代码里是否有延时或阻塞操作?
六、写在最后(关注我,少走弯路)
我是 gqqsherry,一个拒绝调包、专注底层逻辑的嵌入式工程师。
如果说栈溢出是 RTOS 的“急性病”,那么优先级反转就是“慢性病”。不用 Mutex,系统迟早会卡死在某个不可复现的时刻。
关注我的专栏《嵌入式底层避坑指南》,下一篇我们将深入解析 《临界区(Critical Section)卡死?别只怪关中断,看看中断嵌套》。
👉 下一篇预告:《临界区(Critical Section)卡死?别只怪关中断,看看中断嵌套》
原创文章,转载请注明出处。

