第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 使用互斥信号量的注意事项

嗯,这里有几个坑,我帮你提前踩了:

  1. 不要在中断里使用互斥信号量。中断里不能阻塞等待,只能用二值信号量或队列。
  2. 不要长时间持有锁。锁的时间越短越好,否则高优先级任务会被堵死。
  3. 小心死锁。如果任务A拿了锁1,等锁2;任务B拿了锁2,等锁1——两个任务就永远卡死了。
  4. 递归互斥量。如果同一个任务需要多次拿同一个锁,用xSemaphoreCreateRecursiveMutex()

我的习惯:每次拿锁前,我都会想一个问题:「这个锁最多持有多久?」如果超过1ms,我会重新设计代码结构。

4.6 总结一下

互斥信号量,就是FreeRTOS给你的一把「智能锁」。

它知道谁拿了锁,还会帮你处理优先级反转。用对了地方,系统稳得很。

但记住:工具再好,用错了地方也是白搭

保护共享资源,用互斥信号量。任务同步,用二值信号量。别搞混了。

下一章,咱们聊聊递归互斥信号量——那个能让你在同一个任务里反复拿锁的神奇东西。