4. 二进制信号量:创建、获取、释放,解决任务同步问题
好,咱们今天聊聊二进制信号量。
说实话,在VxWorks里做任务同步,二进制信号量是我用得最多的机制。它简单、高效,而且不容易出错。你想想看,两个任务要协调工作,一个等另一个干完活再继续,这种场景太常见了。
4.1 什么是二进制信号量?
二进制信号量,说白了就是一个只有两个状态的旗子:可用 和 不可用。它不像计数信号量那样能记数,它只记0或1。
我个人习惯把它想象成一个「令牌」。谁拿到令牌,谁就能干活。干完了,把令牌还回去。就这么简单。
核心概念:二进制信号量用于任务间的互斥和同步。它只有两种状态:满(1)和空(0)。
4.2 创建二进制信号量
在VxWorks里,创建二进制信号量用的是 semBCreate() 函数。它的原型长这样:
SEM_ID semBCreate
(
int options, /* 信号量选项 */
SEM_B_STATE initialState /* 初始状态:SEM_FULL 或 SEM_EMPTY */
)
这里有两个参数需要你注意:
- options:决定信号量的排队方式。常用的是
SEM_Q_PRIORITY(按优先级排队)和SEM_Q_FIFO(先来后到)。 - initialState:初始状态。如果是同步场景,一般设为
SEM_EMPTY(0);如果是保护共享资源,一般设为SEM_FULL(1)。
我的经验:我一般用 SEM_Q_PRIORITY 做同步,用 SEM_Q_FIFO 做互斥。为什么?因为同步场景下,高优先级的任务应该先拿到信号量;而互斥场景下,大家公平排队就好,避免优先级反转。
4.3 获取信号量
获取信号量用 semTake()。任务调用这个函数时,如果信号量可用(值为1),它就拿到并继续执行;如果不可用(值为0),它就阻塞等待。
STATUS semTake
(
SEM_ID semId, /* 信号量ID */
int timeout /* 超时时间,单位tick */
)
timeout 参数很灵活:
WAIT_FOREVER:死等,直到拿到信号量。NO_WAIT:不等待,拿不到就立即返回。- 具体数值:等待指定的 tick 数,超时后返回 ERROR。
避坑指南:我曾经在一个项目中,把 timeout 设成了 0 而不是 NO_WAIT。结果呢?任务直接返回了 ERROR,但系统没崩溃,只是行为诡异。查了两天才发现是参数传错了。记住:0 不等于 NO_WAIT,NO_WAIT 是宏定义,值通常是 0xffffffff 之类的特殊值。
4.4 释放信号量
释放信号量用 semGive()。任务干完活了,把令牌还回去,让其他等待的任务能拿到。
STATUS semGive
(
SEM_ID semId /* 信号量ID */
)
这个函数很简单,但有个细节要注意:谁拿的谁给。虽然VxWorks不强制要求,但乱给信号量会导致逻辑混乱。
4.5 实战:任务同步示例
咱们来看一个典型的同步场景。任务A负责采集数据,任务B负责处理数据。任务B必须等任务A采集完才能开始处理。
/* 全局信号量 */
SEM_ID semDataReady;
/* 任务A:数据采集 */
void taskA(void)
{
while (1)
{
/* 采集数据 */
collectData();
/* 通知任务B:数据准备好了 */
semGive(semDataReady);
/* 继续采集下一批 */
taskDelay(100);
}
}
/* 任务B:数据处理 */
void taskB(void)
{
while (1)
{
/* 等待数据就绪 */
semTake(semDataReady, WAIT_FOREVER);
/* 处理数据 */
processData();
}
}
/* 初始化 */
void init(void)
{
/* 创建信号量,初始为不可用 */
semDataReady = semBCreate(SEM_Q_PRIORITY, SEM_EMPTY);
/* 创建任务 */
taskSpawn("tA", 100, 0, 4096, (FUNCPTR)taskA, 0,0,0,0,0,0,0,0,0,0);
taskSpawn("tB", 101, 0, 4096, (FUNCPTR)taskB, 0,0,0,0,0,0,0,0,0,0);
}
这段代码里,信号量初始为 SEM_EMPTY。任务B一启动就调用 semTake,但拿不到,于是阻塞。直到任务A采集完数据,调用 semGive,任务B才被唤醒。
关键点:同步场景下,信号量初始状态一定是 SEM_EMPTY。这样消费者任务一开始就会等待,不会空转。
4.6 常见问题与避坑
| 问题 | 现象 | 解决方案 |
|---|---|---|
| 信号量泄漏 | 任务拿了不还 | 检查所有路径,确保 semGive 一定会被执行 |
| 死锁 | 两个任务互相等 | 避免嵌套获取信号量,或使用超时机制 |
| 优先级反转 | 高优先级任务被低优先级任务阻塞 | 使用优先级继承或优先级天花板协议 |
我曾经踩过的坑:有一次,我在中断服务程序里调用了 semGive,但忘了检查返回值。结果信号量给失败了,任务B永远等不到数据。后来加了个 if (semGive(sem) != OK) 的检查,才发现是中断优先级太高,导致信号量操作被阻塞了。记住:中断里操作信号量要小心,最好用 semGive() 而不是 semTake()。
4.7 二进制信号量 vs 互斥信号量
很多初学者会问:二进制信号量和互斥信号量有什么区别?
嗯,这个问题问得好。简单说:
- 二进制信号量:适合做同步。一个任务给,另一个任务拿。没有所有权概念。
- 互斥信号量:适合做互斥。谁拿了谁给,支持优先级继承,防止优先级反转。
我个人习惯是:做同步用二进制信号量,做互斥用互斥信号量。别混用,否则容易出问题。
4.8 小结
二进制信号量是VxWorks里最基础、最常用的同步机制。它只有两个状态,但能解决大部分任务同步问题。
记住三个函数:semBCreate 创建,semTake 获取,semGive 释放。再记住一个原则:同步用 SEM_EMPTY,互斥用 SEM_FULL。
好了,这一章就到这里。下一章咱们聊聊计数信号量,看看它和二进制信号量有什么不同。