4. 互斥信号量:优先级继承机制、递归锁特性、与二进制信号量的区别

好,咱们今天聊互斥信号量。说实话,这是VxWorks里最容易被用错、但又最重要的同步机制之一。我见过太多项目因为没搞清楚互斥信号量的特性,导致系统跑着跑着就卡死了。

先问个问题:你平时用二进制信号量做资源保护,有没有遇到过优先级反转?嗯,如果你遇到过,那互斥信号量就是来救你的。

4.1 优先级继承机制

优先级继承,说白了就是:低优先级任务在持有锁的时候,临时“继承”高优先级任务的优先级。这样就能防止中间优先级的任务插队,导致高优先级任务无限等待。

我举个例子。假设有三个任务:

  • 任务A:优先级10(最高),需要访问共享资源
  • 任务B:优先级20(中等),纯计算任务,不碰资源
  • 任务C:优先级30(最低),已经持有资源锁

如果没有优先级继承,会发生什么?

  1. 任务C先拿到锁,开始干活
  2. 任务A来了,想拿锁,但锁被C占着,A被阻塞
  3. 任务B来了,它优先级比C高,直接抢走CPU
  4. 任务C一直得不到执行,锁释放不了
  5. 任务A就这么干等着...

这就是经典的优先级反转。高优先级任务A,反而被中等优先级的B给“饿死”了。

关键点:互斥信号量的优先级继承机制,会在任务A被阻塞时,把任务C的优先级临时提升到10(和A一样)。这样任务C就能继续执行,尽快释放锁。任务B?它抢不过优先级10的C了。

我在项目中遇到过这种情况:一个传感器数据采集任务(高优先级)总是莫名其妙地超时。查了两天才发现,是中间有个日志打印任务(中等优先级)在捣乱。用了互斥信号量后,问题立刻消失。

我的建议:只要涉及多优先级任务共享资源,优先用互斥信号量。二进制信号量虽然也能用,但风险太大。

4.2 递归锁特性

递归锁,这个特性很多人不知道。它允许同一个任务多次获取同一个互斥信号量,而不会死锁。

你想想看,如果一个函数里调用了另一个函数,两个函数都需要同一个锁,怎么办?

void funcA() {
    semTake(mutex, WAIT_FOREVER);
    // ... 做一些操作 ...
    funcB();  // 这里又需要锁
    semGive(mutex);
}

void funcB() {
    semTake(mutex, WAIT_FOREVER);  // 如果是普通互斥锁,这里就死锁了!
    // ... 做一些操作 ...
    semGive(mutex);
}

如果是普通互斥信号量,任务在funcA里已经拿了锁,再进funcB时,自己把自己锁死了。但互斥信号量支持递归,它知道“哦,还是同一个任务在拿锁”,所以直接放行。

我记得有一次调试一个复杂的协议栈,里面层层嵌套的函数调用,每个函数都要保护共享缓冲区。如果用二进制信号量,我得小心翼翼地设计“先释放再获取”的逻辑,代码改得面目全非。换成互斥信号量后,代码结构清晰多了。

注意:递归锁虽然方便,但每次semTake都要对应一次semGive。拿了几次,就得放几次。我曾经见过有人拿了一次,放了两次,结果把锁提前释放了,其他任务冲进来把数据搞乱了。

4.3 与二进制信号量的区别

好,咱们来做个对比。二进制信号量和互斥信号量,到底有什么区别?

特性 二进制信号量 互斥信号量
优先级继承 不支持 支持
递归获取 不支持(会死锁) 支持
典型用途 任务同步、中断与任务通信 资源互斥访问
释放限制 任何任务都可以释放 只能由持有者释放
性能开销 较低 略高(因为优先级继承需要额外处理)

说白了,二进制信号量更像是一个“通知机制”——我发信号给你,告诉你事情做完了。而互斥信号量是真正的“锁机制”——谁拿了谁负责释放,别人不能乱动。

我个人的习惯是:

  • 保护共享资源(内存、外设、数据结构) → 用互斥信号量
  • 任务间同步(比如ISR通知任务处理数据) → 用二进制信号量

千万别搞混。我曾经接手过一个项目,前人用二进制信号量保护一个全局链表,结果三个任务同时操作,链表指针乱飞,系统隔三差五崩溃。改成互斥信号量后,稳如泰山。

一句话总结:互斥信号量是“带保镖的资源锁”,二进制信号量是“传话的信使”。各司其职,别乱用。

4.4 避坑指南

最后,分享几个我踩过的坑:

  • 不要在ISR里拿互斥信号量。 ISR里不能阻塞,而互斥信号量的semTake可能会阻塞。我曾经这么干过,结果系统直接panic。
  • 注意递归深度。 虽然支持递归,但递归太深会影响性能。我一般控制在3层以内。
  • 优先级继承不是万能的。 如果系统里任务太多,优先级继承链可能很长,反而增加调度开销。这时候可以考虑用优先级天花板协议。

嗯,互斥信号量就聊到这儿。记住一句话:保护资源,用互斥;传递信号,用二进制。 下节课咱们讲计数信号量,那个又是另一种玩法了。