信号量原理:VxWorks信号量类型概述
信号量这东西,说白了就是个「资源令牌」。谁拿到令牌,谁就能干活。拿不到?那就乖乖排队等着。
我在项目中见过不少新手,一上来就开搞任务通信,结果死锁了、优先级反转了,调试到怀疑人生。其实很多问题,都是没搞懂信号量的「性格」造成的。
VxWorks 给了我们三种信号量:二进制、计数、互斥。每种都有自己的脾气。咱们一个一个聊。
1. 二进制信号量:最简单的开关
二进制信号量,值只有 0 和 1。0 表示资源被占,1 表示资源空闲。
你想想看,它像不像一个厕所的「有人/无人」指示灯?
- 任务 A 要使用资源:调用 semTake(),如果信号量是 1,拿走,变成 0;如果是 0,阻塞等待。
- 任务 A 用完资源:调用 semGive(),信号量变回 1,唤醒等待的任务。
我习惯用二进制信号量做「事件通知」。比如一个中断服务程序(ISR)通知一个任务去处理数据。ISR 里不能等,但可以给信号量。任务那边等着,信号量一来,立刻干活。
- 只有两种状态:0 或 1
- 适合互斥访问(但要注意优先级反转)
- 适合事件通知
2. 计数信号量:管理多个资源
计数信号量,值可以是 0 到 N。它管理的是「同类资源的数量」。
举个例子:一个内存池有 5 个缓冲区。5 个任务可能同时来申请。计数信号量的初始值就是 5。每拿走一个,减 1;每归还一个,加 1。当值为 0 时,再来申请的任务就得等着。
我在项目中遇到过这样的情况:一个网络驱动,接收缓冲区有 32 个。用计数信号量管理,完美。每个缓冲区对应一个信号量计数。任务取走缓冲区,计数减 1;ISR 释放缓冲区,计数加 1。从来没出过问题。
- 值范围:0 ~ 设定的最大值
- 适合管理资源池
- 适合生产者-消费者模型
3. 互斥信号量:带「保镖」的二进制信号量
互斥信号量,看起来和二进制信号量很像——也是 0 和 1。但它多了几个「保镖」功能。
为什么需要互斥信号量?因为二进制信号量有个大坑:优先级反转。
什么叫优先级反转?低优先级任务拿着锁,高优先级任务等着。这时候来了个中优先级任务,它不拿锁,但抢占了低优先级任务的 CPU。结果呢?低优先级任务跑不了,锁放不掉,高优先级任务干瞪眼。
我曾经在一个实时控制项目里踩过这个坑。一个高优先级任务要读取传感器数据,结果被一个中优先级的日志任务卡住了。查了两天才发现是优先级反转。从那以后,涉及任务间互斥,我首选互斥信号量。
互斥信号量内置了优先级继承机制。低优先级任务拿到锁后,如果高优先级任务在等,低优先级任务会临时「继承」高优先级任务的优先级。这样中优先级任务就抢不过它了。锁释放后,优先级恢复原样。
- 自带优先级继承,解决优先级反转
- 支持递归获取(同一个任务可以多次 take,但要对应多次 give)
- 只能用于任务间,不能用于 ISR
三种信号量对比
| 特性 | 二进制信号量 | 计数信号量 | 互斥信号量 |
|---|---|---|---|
| 值范围 | 0 或 1 | 0 ~ N | 0 或 1 |
| 优先级继承 | 无 | 无 | 有 |
| ISR 中可用 | 可以(仅 give) | 可以(仅 give) | 不可以 |
| 递归获取 | 不支持 | 不支持 | 支持 |
| 典型用途 | 事件通知、简单互斥 | 资源池管理 | 任务间互斥 |
怎么选?我的经验
- 只是通知一下(比如 ISR 唤醒任务):用二进制信号量。简单、轻量。
- 管理多个同类资源(比如缓冲区、连接):用计数信号量。天然适合。
- 多个任务访问共享数据(比如全局变量、硬件寄存器):用互斥信号量。优先级继承能救你一命。
嗯,这里要注意:别把互斥信号量当万能药。它比二进制信号量重一些,创建和销毁的开销也大。如果只是简单的事件通知,用二进制信号量就够了。
最后说一句:信号量的选择,没有绝对的对错。但选错了,调试起来真的很痛苦。我建议你刚开始做项目时,多花 5 分钟想清楚:这个场景到底需要哪种信号量?想清楚了再写代码,后面能省不少事。