Q: 什么是信号量?
A: 信号量(Semaphore),是在多任务环境下使用的一种机制,是可以用来保证两个或多个关键代码段不被并发调用。 信号量这个名字,我们可以把它拆分来看,“信号”可以起到通知信号的作用,“量”还可以用来表示资源的数量,当“量”只有0和1的时候,它就可以被称作“二值信号量”,只有两个状 ,当我们的那个量没有限制的时候,它就可以被称作为“计数型信号量”。 信号量也是队列的一种。
Q: 什么是二值信号量?
A: 二值信号量其实就是一个长度为1,大小为零的队列,只有0和1两种状态,通常情况下,我们用 它来进行互斥访问或任务同步。
举例理解:
- 互斥访问:比如门钥匙,只有获取到钥匙才可以开门
- 任务同步:比如我写完一本书,别人才可以看这本书
二值信号量相关 API 函数
创建二值信号量
- 返回值: 成功,返回对应二值信号量的句柄; 失败,返回 NULL
释放二值信号量
- xSemaphore:要释放的信号量句柄
- 返回值: 成功,返回 pdPASS ; 失败,返回 errQUEUE_FULL
获取二值信号量
- xSemaphore:要获取的信号量句柄
- xTicksToWait:超时时间(阻塞时间),0 表示不超时,portMAX_DELAY表示卡死等待;
- 返回值: 成功,返回 pdPASS ; 失败,返回 errQUEUE_FULL
实操演示
需求:创建一个二值信号量,按下 KEY1 则释放信号量,按下 KEY2 获取信号量。
在 C:\mjm_CubeMX_proj 路径下,复制一份Cube的母版并重命名为 :mjm_freeRTOS_Sema_Bi:
打开相应的Cube文件:
1. 配置按钮的GPIO:
2. 找到左侧的Middleware --> FREERTOS,然后在下方找到"Task and Queues",然后创建两个任务:
3. 在“Timers and Semaphores” 中创建二值信号量:
4. 生成代码打开Keil:
4.1 在freertos.c中可以看到创建信号量和任务的代码:
注意,此处的osSemaphoreCreate()函数也是Cube对于创建二值信号量函数的封装:
但是有一个问题,上文中提到二值信号量的创建函数是“xSemaphoreCreateBinary()”,但是Cube中封装的对应函数确是“vSemaphoreCreateBinary()”,根据我关于FreeRTOS的函数命名规则的博客可知:本篇上文中提到“x”开头的函数返回的是任务句柄;而Cube中“v”开头的函数返回类型是void型也就是不返回。
我认为之所以Cube使用了“v”开头的函数是因为Cube把返回句柄的功能也封装起来了:
可见上图函数返回的效果也是“成功创建返回句柄,失败返回NULL”
如果跳转这个“vSemaphoreCreateBinary()”:
如上图,和“xSemaphoreCreateBinary()”实现的效果也略有不同,这个“v”开头的函数不仅会创建一个二值信号量,并且如果创建成功,就会自动释放一个信号量。
4.2 代码编写:
#include "stdio.h"
void StartTaskGive(void const * argument)
{
for(;;)
{
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET){
osDelay(20);
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET){ //按键消抖
printf("KEY1 has been pressed\r\n");
if (xSemaphoreGive(myBinarySemHandle) == pdTRUE){ //释放信号量并判断返回值,如果返回成功
printf("successfully give\r\n");
}else{//如果返回失败
printf("fail to give\r\n");
}
}
while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET); //等待按键松开,防止出现按钮一直按着,就一直删除创建任务
}
osDelay(10);
}
}
void StartTaskTake(void const * argument)
{
for(;;)
{
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET){
osDelay(20);
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET){ //按键消抖
printf("KEY2 has been pressed\r\n");
if (xSemaphoreTake(myBinarySemHandle, 0 ) == pdTRUE){ //获取信号量并判断返回值,如果返回成功
printf("successfully take\r\n");
}else{//如果返回失败
printf("fail to take\r\n");
}
}
while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET); //等待按键松开,防止出现按钮一直按着,就一直删除创建任务
}
osDelay(10);
}
}
实现效果
打开串口助手,先尝试按一下KEY1:
发现无法释放,原因就是刚刚提到的,Cube封装的函数在成功创建信号量之后就会直接释放,所以再次释放肯定会失败。
那如果现在按下KEY2:
和预想的一样,因为信号量已经被Cube的代码释放,所以可以成功获取
如果再按一下KEY2:
同时,获取信号量立刻fail的原因是我将获取信号量函数的“xTicksToWait”设置为了0,即只要没有就立刻返回,但是如果我设置为“portMAX_DELAY”,此处就不会打印“fail to take”,而是会一直等待信号量的释放而卡死。
由于信号量在刚刚已经被获取,所以显然不能再获取一次
此时按下KEY1,再按下KEY2:
此时就可以成功释放一个信号量,并成功获取了