一、信号量基础:什么是信号量?为什么需要信号量?VxWorks信号量概述

各位同学好,我是你们的讲师。今天咱们开始聊VxWorks里一个非常核心的话题——信号量。

说实话,我做了十几年嵌入式开发,接触过不少RTOS。但VxWorks的信号量机制,是我用过最灵活、也最容易出问题的一个。嗯,咱们先从最基础的说起。

1.1 什么是信号量?

信号量,英文叫Semaphore。说白了,它就是一个资源计数器

你可以把它想象成一个停车场门口的显示屏:

  • 显示屏上有个数字,显示当前还剩多少个空车位
  • 每进来一辆车,数字减1
  • 每出去一辆车,数字加1
  • 如果数字变成0,后来的车就得排队等着

在VxWorks里,信号量就是这个「显示屏」。任务(Task)就是那些「车」。任务想要访问某个共享资源,就得先看看信号量是否允许。

核心定义:信号量是一个用于任务间同步和互斥的机制,本质上是一个受保护的非负整数变量,支持P(获取)和V(释放)两种原子操作。

这里有个关键点——「原子操作」。什么意思?就是P操作和V操作在执行过程中不会被中断。我在项目中遇到过因为中断服务程序里乱用信号量导致系统崩溃的情况,后面会详细讲。

1.2 为什么需要信号量?

你可能会问:我写单线程程序的时候,根本不需要这东西啊?

没错。但一旦进入多任务环境,问题就来了。

我给大家举三个最常见的场景:

场景一:保护共享资源

假设两个任务都要往同一个串口打印数据:

/* 任务A */
void taskA(void) {
    printf("Hello from Task A\n");
}

/* 任务B */
void taskB(void) {
    printf("Hello from Task B\n");
}

你想想看,如果两个任务同时执行,打印出来的可能是:

Hello Hello from from Task Task A B

乱成一锅粥。这就是典型的竞态条件。信号量就是用来解决这个问题的。

场景二:任务间同步

任务A要等任务B把数据准备好才能继续执行。没有信号量的话,你只能用轮询——浪费CPU不说,还容易出bug。

场景三:控制对有限资源的访问

比如系统里只有3个DMA通道,但有5个任务要用。信号量可以精确控制同时使用的任务数量。

我的经验:刚开始学RTOS的时候,我总觉得信号量可有可无。直到有一次做工业控制项目,两个任务同时修改一个全局变量,导致电机突然反转……嗯,从那以后我再也不敢轻视信号量了。

1.3 VxWorks信号量概述

VxWorks提供了三种信号量,每种都有自己的脾气。我习惯把它们分成三类:

类型 VxWorks名称 特点 典型用途
二进制信号量 SEM_BINARY 只有0和1两种状态 互斥、同步
计数信号量 SEM_COUNTING 可以取0到N之间的任意值 管理多个同类资源
互斥信号量 SEM_MUTEX 带优先级继承,防止优先级反转 保护共享资源(推荐)

这里我要特别强调一下:二进制信号量和互斥信号量不是一回事。很多初学者把它们混为一谈,结果踩了坑。

避坑指南:我曾经在一个项目中用二进制信号量保护共享资源,结果发生了优先级反转——低优先级任务拿着信号量不放,高优先级任务活活被饿死。后来换成互斥信号量,问题才解决。记住:保护共享资源,优先用互斥信号量。

1.4 信号量的基本操作

在VxWorks里,信号量的操作非常直观。我给大家看个最简单的例子:

#include <vxWorks.h>
#include <semLib.h>

SEM_ID semMutex;  /* 信号量ID */

/* 创建互斥信号量 */
semMutex = semMCreate(SEM_Q_PRIORITY | SEM_INVERSION_SAFE);

/* 获取信号量(P操作) */
if (semTake(semMutex, WAIT_FOREVER) == OK) {
    /* 访问共享资源 */
    /* ... */
    
    /* 释放信号量(V操作) */
    semGive(semMutex);
}

这段代码里,semMCreate创建了一个互斥信号量。SEM_Q_PRIORITY表示等待队列按优先级排序,SEM_INVERSION_SAFE启用优先级继承——这两个选项我建议你每次都加上。

semTake就是P操作,semGive就是V操作。名字虽然不同,但意思一样。

1.5 信号量的核心特性

VxWorks的信号量有几个特性,我觉得值得单独拿出来说:

  • 轻量级:信号量是内核对象,但开销很小。创建和删除都非常快。
  • 支持超时:semTake可以指定等待时间,超时了就返回错误。这个特性在防止死锁时特别有用。
  • 支持中断:在ISR(中断服务程序)里可以调用semGive,但不能调用semTake。为什么?因为ISR里不能阻塞。
  • 优先级继承:互斥信号量特有的功能,专门用来解决优先级反转问题。

小技巧:我个人习惯在创建信号量时,总是加上超时参数。即使你确定不会发生死锁,加个超时也能帮你快速定位问题。比如设成10个tick,如果10个tick内没拿到信号量,就打印一条调试信息。这个习惯帮我抓到了不少隐藏的bug。

1.6 什么时候用哪种信号量?

这个问题我经常被问到。我的建议很简单:

  • 保护共享资源:用互斥信号量(semMCreate
  • 任务间同步:用二进制信号量(semBCreate
  • 管理资源池:用计数信号量(semCCreate

举个例子,如果你要保护一个全局变量,用互斥信号量。如果你要让任务A做完某件事后通知任务B,用二进制信号量。如果你要控制最多允许3个任务同时访问某个硬件,用计数信号量,初始值设为3。

好了,这一章的内容就到这里。信号量是VxWorks任务间通信的基石,后面的章节我们会深入每种信号量的具体用法和坑点。记住我一句话:信号量用好了,系统稳如泰山;用不好,bug层出不穷

下一章,咱们聊聊二进制信号量的实战用法。