前面介绍过了计数信号量的定义取值只有0与1两种状态的信号量称之为二值信号量
取值大于1的信号量称之为计数信号量
计数信号量的取值也可以为1,但通常大于1,如果取值为1,相当于只有0与1两种状态,用二值信号量即可。
计数信号量应用场景:
计数信号量允许多个任务对其进行操作,但限制了任务的数量。比如有一个停车场,里面只有50个车位,那么只能停50辆车,相当于我们的计数信号量是50。假如一开始停车场的车位还有50个,那么每进去一辆车就要消耗一个停车位,车位的数量就要减1,相应地,我们的计数信号量在使用之后也需要减 1。当停车场停满了50 辆车时,此时的停车位数量为0,再来的车就不能停进去了,否则将没法停车了,也相当于我们的计数信号量为0,后面的任务对这个停车场资源的访问也无法进行。当有车从停车场离开时,车位又空余出来了,那么后面的车就能停进去了。信号量操作也是一样的,当我们释放了这个资源,后面的任务才能对这个资源进行访问。
任务 Task1 运行过程中调用函数 xSemaphoreTake 获取信号量资源,如果信号量大于0,Task1 将直接获取资源。如果信号量为0,任务 Task1 将由运行态转到阻塞状态,等待资源可用。一旦获取了资源并使用完毕后会通过函数xSemaphoreGive 释放掉资源。
任务 Task2至TaskN 等N个任务运行过程中调用函数 xSemaphoreTake 获取信号量资源,如果信号量大于0,Task2至N 将直接获取资源。如果信号量为0,任务 Task2至N将由运行态转到阻塞状态,等待资源可用。一旦获取了资源并使用完毕后会通过函数 xSemaphoreGive释放掉资源。
计数信号量使用流程和常用的API函数
- 创建计数信号量
xSemaphoreCreateCounting()
- 释放计数信号量
xSemaphoreGive()与xSemaphoreGiveFromlSR()
- 获取计数信号量
xSemaphoreTake()
- 删除计数信号量
vSemaphoreDelete()
1、计数信号量创建与删除
1、计数信号量控制块(句柄)
如下图:计数信号量的句柄为消息队列的句柄,因为计数信号量是一种长度大于1,消息大小为0的特殊消息队列
//FreeRtos源码中定义的句柄
typedef QueueHandle_t semaphoreHandle_t,
在程序中定义计数信号量
//定义计数信号量
osSemaphoreId myCountingSem01Handle = NULL;
2、计数信号量创建
函数原型:
Semaphorelandle_t xSemaphoreCreateCounting(UBaseType_t uxllaxCount,/*支持的最大计数值 */UBascTypc_t uxInitialCount);/*初始计数值 */
函数描述:
函数 xSemaphoreCreateCounting 用于创建计数信号量。
第 1个参数是设置此计数信号量支持的最大计数值。
第 2 个参数是设置计数信号量的初始值。这里的初始值设置就是从0到第一个参数设置的最大计数值uxllaxCount
返回值,如果创建成功会返回消息队列的句柄,如果由于 FreeRTOSConfig.h 文件中heap 大小不足,无法为此消息队列提供所需的空间会返回 NULL
应用举例:
myCountingSem01Handle = xSemaphoreCreateCounting(20,20)
if(myCountingSem01Handle == NULL)printf("创建计数信号量失败\r\n\r\n");
elseprintf("创建计数信号量成功\r\n\r\n");
3、计数信号量删除
函数原型:
void vSemaphoreDeete(SemaphoreHandle_t xSemaphore); /* 参数为计数信号量句枘 */
函数描述:
函数 vSemaphoreDelete
可用于删除计数信号量。
4、任务中计数信号量释放,当前计数值加加
函数原型:
xSemaphoreGive(SemaphoreHandle_t xSemaphore); /*参数使用计数信号量柄*/
函数描述:
函数 xSemaphoreGive 用于在任务代码中释放信号量。
参数是信号量句柄。
返回值,如果信号量释放成功返回 pdTRUE,否则返回 pdFALSE,因为信号量的实现是基于消息队列,返回失败的主要原因是消息队列已经满了。
使用这个函数要注意以下问题:
1.此函数是用于任务代码中调用的,故不可以在中断服务程序中调用此函数,中断服务程序中使用的是xSemaphoreGiveFromISR.
2.使用此函数前,一定要保证用函数 xSemaphoreCreateBinary(),xSemaphoreCreate-Mutex() 或者xSemaphoreCreateCounting()创建了信号量。
3.此函数不支持使用 xSemaphoreCreateRecursiveMutex()创建的信号量。
应用举例:
printf("释放计数信号量,模拟车辆出库,让出停位");
xResult=xSemaphoreGive(myCountingSemlHandle);
if(xResult == pdTRUE)
{printf("释放成功,成功让出停车位,发送同步显示信号\r\n");
}
else
{printf("释放失败,停车位已空\r\n");
}
5、中断中计数信号量释放
函数原型:
xSemaphoreGiveFromISR(Semaphorellandle t xSemaphore,/* 信号量句柄*/
signed BaseType_t *pxHigherPriorityTaskWoken /* 高优先级任务是否被唤醒的状态保存 */)
函数描述:
函数 xSemaphoreGiveFromlSR 用于中断服务程序中释放信号量。
第 1个参数是信号量句柄。
第 2 个参数用于保存是否有高优先级任务准备就绪。如果函数执行完毕后,此参数的数值是 PdTRUE,说明有高优先级任务要执行,否则没有。返回值,如果信号量释放成功返回 pdTRUE,否则返回 errQUEUE FULL
使用这个函数要注意以下问题:
1.此函数是基于消息队列函数 xQueueGiveFromISR 实现的:#define xSemaphore-GiveFromlSR( xSemaphore, pxHigherPriorityTaskWoken ) \xQueueGiveFromlSR( (QueueHandle t )( xSemaphore ),( pxHigherPriorityTaskWoken ))
2.此函数是用于中断服务程序中调用的,故不可以任务代码中调用此函数,任务代码中中使用的是xSemaphoreGive。
3.使用此函数前,一定要保证用函数xSemaphoreCreateBinary()或者xSemaphoreCreateCounting()创建了信号量。
4.此函数不支持使用 xSemaphoreCreateMutex()创建的信号量.
6、计数信号量获取,计数值减减,计数值为0的话就获取不到
函数原型:
xSemaphoreTake( SemaphoreHandle_t xSemaphore,/*信号量句柄*/
TickType_t xTicksToWait ):/*等待信号量可用的最人等待时间 */
函数描述:
函数 xSemaphoreTake 用于在任务代码中获取信号量。
第1个参数是信号量句柄。
第 2 个参数是没有信号量可用时,等待信号量可用的最大等待时间,单位系统时钟节拍。
返回值,如果创建成功会获取信号量返回 pdTRUE,否则返回 pdFALSE。
使用这个函数要注意以下问题:
1.此函数是用于任务代码中调用的,故不可以在中断服务程序中调用此函数,中断服务程序使用的是xSemaphoreTakeFromISR。
2.如果消息队列为空且第 2 个参数为 0,那么此函数会立即返回。
3.如果用户将 FreeRTOSConfig.h 文件中的宏定义 INCLUDE_vTaskSuspend 配置为 1且第 2个参数配置为 portMAX_DELAY,那么此函数会永久等待直到信号量可用。
应用举例:
printf("获取计数信号量,拟车辆入库,申请停车位");
xResult=xSemaphoreTake(myCountingSem01Handle,0);
//返回值为TRUE说明计数值不为0,但此时计数值会减1,为FLASE说明计数值为0,此时其他任务或中断还没有释放计数信号量,需要根据设置的时间进行等待
if(xResult == pdTRUE)
{printf("获取成功,成功申请停车位,发送同步显示信号\r\n");xSemaphoreGive(myBinarysem01Handle);myCountingSem_ucMessagesWaiting--;
}
elseprintf("获取失败,停车位已满\r\n");
计数信号量使用的案例
假设现在停车场有20个停车位
osSemaphoreId myCountingSem01Handle = NULL;
myCountingSem01Handle = xSemaphoreCreateCounting(20,20);
这里就是设置第一个参数最大值为20,初始值为20
当有车要进来的时候,就会获取信号量(计数值减一),当有车出去的时候就会释放信号量(信号量加一)
这里为了方便查看还有多少个空位置,见了一个初始值为20的myCountingSem_ucMessagesWaiting
变量,这个变量的值会在显示屏显示出来,当有车要进来的时候计数信号量获取会减减,此时会将这个变量减1,相反车出去时会加一,但是有一个问题,这个显示在另外一个任务中实现的,所以就需要使用二值信号量进行同步保护
按键检测任务
extern int myCountingSem_ucMessagesWaiting; //外部引用
//KEY3处理
if(KEY.KeyCode == KEY3)
{printf("获取计数信号量,模拟车辆入库,申请停车位");xResult =xSemaphoreTake(myCountingSem01Handle,0);if(xResult == pdTRUE){printf("获取成功,成功申请停车位,发送同步显示信号\r\n");xSemaphoreGive(myBinarySem01Handle); //使用二值信号量保护当前空余位置数量myCountingSem_ucMessagesWaiting--;}elseprintf("获取失败,停车位已满\r\n");
}
//KEY4处理
if(KEY.KeyCode == KEY4)
{printf("释放计数信号量,模拟车辆出库,让出停车位");xResult=xSemaphoreGive(myCountingSem01Handle);if(xResult == pdTRUE){printf("释放成功,成功让出停车位,发送同步显示信号\r\n");xSemaphoreGive(myBinarysem1Handle);myCountingSem_ucMessagesWaiting++;}elseprintf("释放失败,停车位已空\r\n");
}
int myCountingSem_ucMessagesWaiting;
void play()
{while(1){BaseType_t xResult;//获取二值信号量,成功会让二值信号量变为0,成功说明获取到了,如果为0,就需要等待其他任务或者中断将二值信号量释放xResult = xSemaphoreTake(myBinarySem01Handle,portMAX DELAY); if(xResult == pdTRUE){screen_play(myCountingSem_ucMessagesWaiting);}}
}