第4章:互斥信号量——保护共享资源的正确姿势
互斥信号量,说白了就是一把「专用锁」。
我在项目中见过太多人把它跟二值信号量搞混。嗯,今天咱们就把这事彻底说清楚。
4.1 互斥信号量的工作原理
互斥信号量(Mutex)跟二值信号量长得像,但内核处理方式完全不同。
它的核心规则只有一条:谁拿了,谁才能放。
具体流程是这样的:
- 任务A想访问共享资源,先拿互斥锁
- 如果锁空闲,任务A拿到锁,进入临界区
- 其他任务再想拿,就得排队等着
- 任务A用完了,必须自己释放锁
你想想看,如果任务A拿了锁,任务B却跑去释放,那不乱套了吗?
关键区别:互斥信号量有「所有权」概念。内核会记录当前哪个任务持有锁。只有这个任务才能释放它。
4.2 优先级继承机制——这才是精华
优先级继承,是互斥信号量最牛的地方。
我刚开始用FreeRTOS时,觉得二值信号量也能保护资源啊,干嘛非要用互斥?直到遇到了优先级反转……
什么是优先级反转?
假设有三个任务:
- 任务H(高优先级)
- 任务M(中优先级)
- 任务L(低优先级)
任务L先拿到了锁。任务H来了,想拿锁,但拿不到,只能等。这时候任务M开始运行了——它不需要锁,但它优先级比L高,所以把L抢占了。
结果呢?高优先级的H,反而被中优先级的M给「踩」了。这就是优先级反转。
互斥信号量怎么解决?
当任务H因为拿不到锁而阻塞时,内核会临时把任务L的优先级提升到跟H一样高。
这样一来,任务M就抢不过L了。L赶紧跑完,释放锁,H拿到锁继续执行。
我的经验:优先级继承不是万能的。它只能缓解反转,不能完全消除。但绝大多数场景下,够用了。
4.3 与二值信号量的区别
很多人问我:「二值信号量也能做互斥啊,为啥还要单独搞个互斥信号量?」
区别大了去了。我列个表给你看:
| 特性 | 互斥信号量 | 二值信号量 |
|---|---|---|
| 所有权 | 有,必须谁拿谁放 | 无,任何任务都能给 |
| 优先级继承 | 支持 | 不支持 |
| 递归获取 | 支持(递归互斥量) | 不支持 |
| 典型用途 | 保护共享资源 | 任务同步、事件通知 |
| 释放者限制 | 只能持有者释放 | 任何任务都能释放 |
说白了,互斥信号量是「锁」,二值信号量是「旗」。
锁要管谁在用,旗只管有没有信号。
4.4 典型应用场景——保护共享资源
互斥信号量最常见的用法,就是保护那些不能同时被多个任务访问的资源。
比如:
- 全局变量(尤其是结构体)
- 硬件外设(串口、SPI、I2C)
- 内存缓冲区
- 文件系统
我举个例子:
// 定义一个互斥信号量句柄
SemaphoreHandle_t xSerialMutex;
// 创建互斥信号量
xSerialMutex = xSemaphoreCreateMutex();
// 任务A:发送数据
void vTaskA(void *pvParameters) {
// 拿锁
if (xSemaphoreTake(xSerialMutex, portMAX_DELAY) == pdTRUE) {
// 安全使用串口
printf("Task A: Hello\r\n");
// 放锁
xSemaphoreGive(xSerialMutex);
}
}
// 任务B:发送数据
void vTaskB(void *pvParameters) {
if (xSemaphoreTake(xSerialMutex, portMAX_DELAY) == pdTRUE) {
printf("Task B: World\r\n");
xSemaphoreGive(xSerialMutex);
}
}
你看,两个任务都想用串口。没有互斥锁的话,打印出来的内容会混在一起,变成「HTaeskl lAo: W Hoerlllod」这种鬼样子。
避坑指南:我曾经在一个项目里,用二值信号量保护串口。结果一个任务拿了信号量,另一个任务却把它释放了。数据全乱了。排查了两天才发现是信号量类型用错了。
记住:保护共享资源,永远用互斥信号量,别用二值信号量。
4.5 使用互斥信号量的注意事项
嗯,这里有几个坑,我帮你提前踩了:
- 不要在中断里使用互斥信号量。中断里不能阻塞等待,只能用二值信号量或队列。
- 不要长时间持有锁。锁的时间越短越好,否则高优先级任务会被堵死。
- 小心死锁。如果任务A拿了锁1,等锁2;任务B拿了锁2,等锁1——两个任务就永远卡死了。
- 递归互斥量。如果同一个任务需要多次拿同一个锁,用
xSemaphoreCreateRecursiveMutex()。
我的习惯:每次拿锁前,我都会想一个问题:「这个锁最多持有多久?」如果超过1ms,我会重新设计代码结构。
4.6 总结一下
互斥信号量,就是FreeRTOS给你的一把「智能锁」。
它知道谁拿了锁,还会帮你处理优先级反转。用对了地方,系统稳得很。
但记住:工具再好,用错了地方也是白搭。
保护共享资源,用互斥信号量。任务同步,用二值信号量。别搞混了。
下一章,咱们聊聊递归互斥信号量——那个能让你在同一个任务里反复拿锁的神奇东西。