在VxWorks里用信号量,很多人前面的问题不是不会写`semTake`和`semGive`,而是一开始就把信号量类型选错了。因为VxWorks本来就把信号量分成二值、计数和互斥三类,创建入口也分别是`semBCreate()`、`semCCreate()`和`semMCreate()`。这三类东西表面都能“卡住任务”,但用途并不一样:二值更适合同步和一般互斥,计数更适合表示多个同类资源,互斥信号量则专门面向共享资源保护和优先级反转处理。先把类型选对,后面再谈优先级反转,路子才不会乱。
一、VxWorks信号量怎么创建
创建信号量时,不要一上来只记API名字,更重要的是先判断当前场景到底是“等一个事件”,还是“管一把锁”,还是“管一批资源”。VxWorks的官方参考里把三类信号量的行为边界写得很清楚,所以更稳的做法,是先按用途分流,再去写创建参数。
1、做任务同步时先用二值信号量
如果你的需求是一个任务等另一个任务,或者任务等中断服务例程发事件,优先用`semBCreate()`。官方参考说明,二值信号量只有满和空两种状态,创建时初始状态可以设成`SEM_EMPTY`或`SEM_FULL`;用于同步时,更常见的是先建成空,再由别的任务或ISR调`semGive()`把等待任务唤醒,而且ISR只能给、不能取。
2、做多个资源计数时再用计数信号量
如果你手里不是“一把锁”,而是多个同类资源,比如若干缓冲区、若干设备通道或若干连接槽位,就更适合用`semCCreate()`。官方参考把计数信号量定义得很直接:内部维护一个计数值,`semTake()`会递减,计数到零时任务阻塞,`semGive()`会递增或唤醒等待任务。也就是说,它更像“库存计数器”,不是严格意义上的互斥锁。
3、做共享资源保护时优先用互斥信号量
如果你的目标是保护共享数据结构、共享设备或关键临界区,更稳的是直接用`semMCreate()`,不要长期拿普通二值信号量去硬顶。官方说明,互斥信号量就是为mutual exclusion准备的,它还支持优先级继承、删除保护和递归访问,这些都是普通二值信号量没有原生保证的能力。
4、排队方式创建时就一起定
无论是`semBCreate()`、`semCCreate()`还是`semMCreate()`,创建时都要带排队方式。官方资料说明,等待任务可以按`SEM_Q_FIFO`或`SEM_Q_PRIORITY`排队;如果你后面还要用优先级继承,那么互斥信号量必须配合`SEM_Q_PRIORITY`。也就是说,队列策略不是附属项,而是创建时就该想清楚的基本条件。
二、VxWorks怎么避免信号量优先级反转
优先级反转真正麻烦的地方,不在于“高优先级任务被挡住了”这件事本身,而在于低优先级任务拿着资源时,还可能被中优先级任务不断抢走CPU,最后把高优先级任务拖得很久。VxWorks官方对这个场景的说明非常明确,解决办法不是继续用普通二值信号量硬扛,而是让资源拥有者在必要时自动继承更高优先级。
1、先把共享资源从二值信号量切到互斥信号量
如果当前保护共享资源用的是`semBCreate()`,而且系统里已经有明显的高、中、低优先级任务交错运行,那就不要继续指望靠任务习惯去规避反转。更稳的做法是直接换成`semMCreate()`,因为官方把优先级继承能力明确放在互斥信号量这一类里。
2、创建时开启`SEM_INVERSION_SAFE`
官方文档写得很直接,优先级继承是通过`SEM_INVERSION_SAFE`打开的。它的作用是:当高优先级任务阻塞在某个资源上时,当前持有该资源的低优先级任务会被临时提升到被阻塞任务的优先级,从而避免被中优先级任务反复抢占。
3、同时必须配上`SEM_Q_PRIORITY`
这一点很容易漏,但官方说得很明确:`SEM_INVERSION_SAFE`必须和`SEM_Q_PRIORITY`一起使用。也就是说,真正稳定的写法不是只开一个inversion safe,而是像官方示例那样创建:`semMCreate(SEM_Q_PRIORITY|SEM_INVERSION_SAFE)`。少了优先级队列,优先级继承这一层就站不稳。
4、需要时再加`SEM_DELETE_SAFE`
如果你的临界区不只怕反转,还怕任务在持锁状态下被删掉,那么互斥信号量还可以继续叠`SEM_DELETE_SAFE`。官方说明,这个选项会在任务持有互斥信号量期间隐式提供删除保护,避免任务在关键区被删后把资源留在不一致状态。它不是解决优先级反转的核心,但在真实系统里经常和优先级继承一起考虑。
三、VxWorks先选哪类信号量
真正容易把系统带偏的,往往不是不会开`SEM_INVERSION_SAFE`,而是前面那一步“到底该用哪类信号量”先做反了。明明是共享资源竞争,却先上二值信号量;明明是多个资源计数,却直接拿互斥锁包一层。更稳的判断方法其实很简单,就是先问自己:我现在是在等事件,还是在抢资源,还是在数资源。这个顺序一清楚,很多后面的优先级反转问题其实在创建那一刻就已经被规避掉一大半了。
1、等事件就用`semBCreate()`
任务同步、ISR通知任务、某个条件达成再继续跑,这些都更适合二值信号量。这里重点是“有人发信号、有人等信号”,不是资源独占。
2、数资源就用`semCCreate()`
资源不是一个,而是一组时,计数信号量更自然。它解决的是“还剩几个”,不是“谁正在独占”。
3、抢资源就用`semMCreate()`
只要进入共享资源保护,尤其是有不同优先级任务同时竞争时,就优先上互斥信号量,再按需要叠`SEM_INVERSION_SAFE`和`SEM_DELETE_SAFE`。这一步越早做对,后面越不容易在实时性上吃亏。
总结
VxWorks里创建信号量,真正该先想清楚的不是函数名,而是用途。同步事件用`semBCreate()`,多个资源计数用`semCCreate()`,共享资源互斥优先用`semMCreate()`。至于优先级反转,最稳的处理方式也不是事后补救,而是在互斥信号量创建时就把`SEM_Q_PRIORITY|SEM_INVERSION_SAFE`配好;如果关键区还怕任务被删,再把`SEM_DELETE_SAFE`一起带上。把类型和选项在创建阶段就收对,后面的调度问题通常会少很多。
