二值信号量:任务同步的“开关”
二值信号量,名字听着挺唬人,其实说白了就是一个只有两个状态的“开关”。要么是1(有效),要么是0(无效)。在FreeRTOS里,它最核心的用途就是——任务同步。
我刚开始用RTOS那会儿,总觉得信号量是个很玄乎的东西。后来踩了几次坑才明白,二值信号量本质上就是个“通知机制”。一个任务负责“给”,另一个任务负责“等”。就这么简单。
工作原理:就像一把钥匙
想象一下这个场景:你有一把钥匙,这把钥匙只能被一个人拿着。当钥匙在桌上时,你可以拿走它(Take)。当你用完,你把它放回桌上(Give)。
二值信号量就是这把钥匙。它的值只有两个:
- 1:信号量可用,任务可以获取
- 0:信号量被占用,任务需要等待
当一个任务调用xSemaphoreTake()时,如果信号量是1,它就立刻拿到,并把值变成0。如果已经是0,任务就会被挂起,直到有人调用xSemaphoreGive()把它变回1。
核心要点:二值信号量不计数。它只记录“有没有”,不记录“有多少”。这和计数信号量有本质区别。
创建与删除:从生到死
创建二值信号量,我习惯用xSemaphoreCreateBinary()这个API。它会在堆上动态分配内存。
SemaphoreHandle_t xBinarySemaphore;
// 创建二值信号量
xBinarySemaphore = xSemaphoreCreateBinary();
// 检查是否创建成功
if (xBinarySemaphore != NULL) {
// 创建成功,可以用了
} else {
// 内存不足,创建失败
// 我曾经在这里吃过亏,堆大小没配够
}
创建时,信号量的初始值是0。这意味着第一个调用xSemaphoreTake()的任务会阻塞,直到有人先Give一次。
删除就简单了:
// 删除信号量
vSemaphoreDelete(xBinarySemaphore);
xBinarySemaphore = NULL; // 我习惯置空,防止野指针
注意:删除信号量时,如果有任务正在等待这个信号量,这些任务会永远等下去。我曾经在项目里犯过这个错,调试了一整天才发现是信号量被提前删了。
Give与Take操作:给与拿的艺术
这两个操作是二值信号量的灵魂。咱们一个一个说。
Give(给)
xSemaphoreGive()用于释放信号量。如果当前信号量是0,调用后变成1。如果有任务在等待,这个任务会被立即唤醒。
// 从中断中释放信号量
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(xBinarySemaphore, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
嗯,这里要注意:中断里只能用GiveFromISR版本。我见过有人直接在中断里调xSemaphoreGive(),结果系统直接崩了。
Take(拿)
xSemaphoreTake()用于获取信号量。你可以设置超时时间:
// 无限等待,直到拿到信号量
xSemaphoreTake(xBinarySemaphore, portMAX_DELAY);
// 等待100个tick,拿不到就超时返回
if (xSemaphoreTake(xBinarySemaphore, pdMS_TO_TICKS(100)) == pdTRUE) {
// 拿到了,干活
} else {
// 超时了,处理超时逻辑
}
我的习惯:能用超时就用超时,别用portMAX_DELAY。万一信号量永远不来,任务就卡死了。调试的时候特别痛苦。
典型应用场景:任务同步
二值信号量最常见的用法就是“一个任务等另一个任务干完活”。我给你举个例子:
假设你有两个任务:一个负责采集数据(生产者),一个负责处理数据(消费者)。采集任务必须等处理任务完成上一批数据,才能开始下一批采集。
// 生产者任务
void vProducerTask(void *pvParameters) {
for (;;) {
// 采集数据
collectData();
// 通知消费者:数据准备好了
xSemaphoreGive(xBinarySemaphore);
// 等消费者处理完
xSemaphoreTake(xBinarySemaphore, portMAX_DELAY);
}
}
// 消费者任务
void vConsumerTask(void *pvParameters) {
for (;;) {
// 等数据
xSemaphoreTake(xBinarySemaphore, portMAX_DELAY);
// 处理数据
processData();
// 通知生产者:我处理完了,你可以继续了
xSemaphoreGive(xBinarySemaphore);
}
}
你看,这两个任务通过一个二值信号量,实现了“你等我,我等你”的同步。这就是所谓的“握手协议”。
避坑指南:我曾经在项目里用二值信号量做任务同步,结果发现两个任务都卡死了。查了半天才发现——我忘了在创建信号量后先Give一次。创建时信号量默认是0,两个任务都在等对方Give,结果谁也等不到。死锁了。
什么时候用二值信号量?
我总结了几条经验:
- 任务同步:一个任务等另一个任务完成某个动作
- 中断与任务同步:中断里
Give,任务里Take - 一次性事件:比如外设初始化完成,通知主任务开始工作
但要注意,二值信号量不适合做资源管理。如果你有多个任务要访问同一个资源,用互斥量更合适。二值信号量没有优先级继承机制,容易出优先级反转的问题。
警告:别把二值信号量当互斥量用。我见过有人这么干,结果高优先级任务被低优先级任务堵死,系统响应时间直接爆炸。
好了,二值信号量就聊到这儿。说白了它就是一把钥匙,谁拿到谁干活。用好了是利器,用不好就是坑。你想想看,是不是这个理儿?