信号量原理:VxWorks信号量类型概述

信号量这东西,说白了就是个「资源令牌」。谁拿到令牌,谁就能干活。拿不到?那就乖乖排队等着。

我在项目中见过不少新手,一上来就开搞任务通信,结果死锁了、优先级反转了,调试到怀疑人生。其实很多问题,都是没搞懂信号量的「性格」造成的。

VxWorks 给了我们三种信号量:二进制、计数、互斥。每种都有自己的脾气。咱们一个一个聊。

1. 二进制信号量:最简单的开关

二进制信号量,值只有 0 和 1。0 表示资源被占,1 表示资源空闲。

你想想看,它像不像一个厕所的「有人/无人」指示灯?

  • 任务 A 要使用资源:调用 semTake(),如果信号量是 1,拿走,变成 0;如果是 0,阻塞等待。
  • 任务 A 用完资源:调用 semGive(),信号量变回 1,唤醒等待的任务。

我习惯用二进制信号量做「事件通知」。比如一个中断服务程序(ISR)通知一个任务去处理数据。ISR 里不能等,但可以给信号量。任务那边等着,信号量一来,立刻干活。

核心特点:
  • 只有两种状态:0 或 1
  • 适合互斥访问(但要注意优先级反转)
  • 适合事件通知
我的小技巧: 二进制信号量初始化时,如果初始值是 0,那就是「事件未发生」;如果是 1,那就是「资源可用」。别搞反了,否则任务一启动就卡死。

2. 计数信号量:管理多个资源

计数信号量,值可以是 0 到 N。它管理的是「同类资源的数量」。

举个例子:一个内存池有 5 个缓冲区。5 个任务可能同时来申请。计数信号量的初始值就是 5。每拿走一个,减 1;每归还一个,加 1。当值为 0 时,再来申请的任务就得等着。

我在项目中遇到过这样的情况:一个网络驱动,接收缓冲区有 32 个。用计数信号量管理,完美。每个缓冲区对应一个信号量计数。任务取走缓冲区,计数减 1;ISR 释放缓冲区,计数加 1。从来没出过问题。

核心特点:
  • 值范围:0 ~ 设定的最大值
  • 适合管理资源池
  • 适合生产者-消费者模型
注意: 计数信号量的最大值在创建时设定。如果设得太小,任务可能频繁阻塞;设得太大,浪费内存。我一般根据实际资源数量来定,比如缓冲区个数、连接数等。

3. 互斥信号量:带「保镖」的二进制信号量

互斥信号量,看起来和二进制信号量很像——也是 0 和 1。但它多了几个「保镖」功能。

为什么需要互斥信号量?因为二进制信号量有个大坑:优先级反转

什么叫优先级反转?低优先级任务拿着锁,高优先级任务等着。这时候来了个中优先级任务,它不拿锁,但抢占了低优先级任务的 CPU。结果呢?低优先级任务跑不了,锁放不掉,高优先级任务干瞪眼。

我曾经在一个实时控制项目里踩过这个坑。一个高优先级任务要读取传感器数据,结果被一个中优先级的日志任务卡住了。查了两天才发现是优先级反转。从那以后,涉及任务间互斥,我首选互斥信号量。

互斥信号量内置了优先级继承机制。低优先级任务拿到锁后,如果高优先级任务在等,低优先级任务会临时「继承」高优先级任务的优先级。这样中优先级任务就抢不过它了。锁释放后,优先级恢复原样。

核心特点:
  • 自带优先级继承,解决优先级反转
  • 支持递归获取(同一个任务可以多次 take,但要对应多次 give)
  • 只能用于任务间,不能用于 ISR
避坑指南: 我曾经在 ISR 里尝试用互斥信号量,结果系统直接崩溃。互斥信号量涉及任务调度,ISR 里不能用。ISR 里只能用二进制或计数信号量做通知。

三种信号量对比

特性 二进制信号量 计数信号量 互斥信号量
值范围 0 或 1 0 ~ N 0 或 1
优先级继承
ISR 中可用 可以(仅 give) 可以(仅 give) 不可以
递归获取 不支持 不支持 支持
典型用途 事件通知、简单互斥 资源池管理 任务间互斥

怎么选?我的经验

  • 只是通知一下(比如 ISR 唤醒任务):用二进制信号量。简单、轻量。
  • 管理多个同类资源(比如缓冲区、连接):用计数信号量。天然适合。
  • 多个任务访问共享数据(比如全局变量、硬件寄存器):用互斥信号量。优先级继承能救你一命。

嗯,这里要注意:别把互斥信号量当万能药。它比二进制信号量重一些,创建和销毁的开销也大。如果只是简单的事件通知,用二进制信号量就够了。

最后说一句:信号量的选择,没有绝对的对错。但选错了,调试起来真的很痛苦。我建议你刚开始做项目时,多花 5 分钟想清楚:这个场景到底需要哪种信号量?想清楚了再写代码,后面能省不少事。