freertos学习笔记十之信号量(计数信号量)(二值信号量)(互斥信号量)(任务计数信号量)

· 更新于

freertosmutexsemaphore互斥信号量任务间信号量信号量计数信号量

1.1信号量Semaphore

信号量说白了就 是共享资源的数量。

平时使用信号量主要实现以下两个功能:

  • 两个任务之间或者中断函数跟任务之间的同步功能,这个和前面章节讲解的事件标志组是类似的。其 实就是共享资源为 1 的时候。
  • 多个共享资源的管理

FreeRTOS 分别提供了二值信号量和计数信号量

1.2FreeRTOS 任务间计数信号量的实现

任务间信号量的实现是指各个任务之间使用信号量实现任务的同步或者资源共享功能

运行条件:  创建 2 个任务 Task1 和 Task2。 创建计数信号量可用资源为 1。

运行过程描述如下:  任务 Task1 运行过程中调用函数 xSemaphoreTake 获取信号量资源,如果信号量没有被任务 Task2 占用,Task1 将直接获取资源。如果信号量被 Task2 占用,任务 Task1 将由运行态转到阻塞状态, 等待资源可用。一旦获取了资源并使用完毕后会通过函数 xSemaphoreGive 释放掉资源。  任务 Task2 运行过程中调用函数 xSemaphoreTake 获取信号量资源,如果信号量没有被任务 Task1 占用,Task2 将直接获取资源。如果信号量被 Task1 占用,任务 Task2 将由运行态转到阻塞状态, 等待资源可用。一旦获取了资源并使用完毕后会通过函数 xSemaphoreGive 释放掉资源。

1.3FreeRTOS 中断方式计数信号量的实现

信号量的中断方 式主要是用于实现任务同步,与前面章节讲解事件标志组中断方式是一样的。

运行条件:

1 ,创建一个任务 Task1 和一个串口接收中断。

2, 信号量的初始值为 0,串口中断调用函数 xSemaphoreGiveFromISR 释放信号量,任务 Task1 调用 函数 xSemaphoreTake 获取信号量资源。

运行过程描述如下:

  1. 任务 Task1 运行过程中调用函数 xSemaphoreTake,由于信号量的初始值是 0,没有信号量资源可 用,任务 Task1 由运行态进入到阻塞态。
  2. Task1 阻塞的情况下,串口接收到数据进入到了串口中断服务程序,在串口中断服务程序中调用函数 xSemaphoreGiveFromISR 释放信号量资源,信号量数值加 1,此时信号量计数值为 1,任务 Task1 由阻塞态进入到就绪态,在调度器的作用下由就绪态又进入到运行态,任务 Task1 获得信号量后,信 号量数值减 1,此时信号量计数值又变成了 0。
  3. 再次循环执行时,任务 Task1 调用函数 xSemaphoreTake 由于没有资源可用再次进入到阻塞态,等 待串口释放信号量资源,如此往复循环。

中断方式的消息机制要注意以下 四个问题:

  • 中断函数的执行时间越短越好,防止其它低于这个中断优先级的异常不能得到及时响应。
  • 实际应用中,建议不要在中断中实现消息处理,用户可以在中断服务程序里面发送消息通知任务,在 任务中实现消息处理,这样可以有效地保证中断服务程序的实时响应。同时此任务也需要设置为高优 先级,以便退出中断函数后任务可以得到及时执行。

1.4计数信号量 APII 函数

函数 xSemaphoreCreateCounting

SemaphoreHandle_t xSemaphoreCreateCounting( 
           UBaseType_t uxMaxCount, /* 支持的最大计数值 */
           UBaseType_t uxInitialCount); /* 初始计数值 */

函数 xSemaphoreCreateCounting 用于创建计数信号量。

  • 第 1 个参数是设置此计数信号量支持的最大计数值。
  • 第 2 个参数是设置计数信号量的初始值。
  • 返回值,如果创建成功会返回消息队列的句柄,如果由于 FreeRTOSConfig.h 文件中 heap 大小不足, 无法为此消息队列提供所需的空间会返回 NULL。
static SemaphoreHandle_t xSemaphore = NULL;
static void AppObjCreate (void)
{
    /* 初始化有 1 个可用资源,当前可用资源为 0,此时计数信号量的功能等同二值信号量 */
    xSemaphore = xSemaphoreCreateCounting(1, 0);
    if(xSemaphore == NULL)
    {
     /* 没有创建成功,用户可以在这里加入创建失败的处理机制 */
    } 
}

函数 xSemaphoreGive

xSemaphoreGive( SemaphoreHandle_t xSemaphore ); /* 信号量句柄 */

函数 xSemaphoreGive 用于在任务代码中释放信号量。

  • 使用此函数前,一定要保证用函数 xSemaphoreCreateBinary(), xSemaphoreCreateMutex() 或者 xSemaphoreCreateCounting()创建了信号量。
  • 此函数不支持使用 xSemaphoreCreateRecursiveMutex()创建的信号量。

函数 xSemaphoreGiveFromISR

xSemaphoreGiveFromISR
 (
 SemaphoreHandle_t xSemaphore, /* 信号量句柄 */
 signed BaseType_t *pxHigherPriorityTaskWoken /* 高优先级任务是否被唤醒的状态保存 */
 )

函数 xSemaphoreGiveFromISR 用于中断服务程序中释放信号量。

  • 第 1 个参数是信号量句柄。
  • 第 2 个参数用于保存是否有高优先级任务准备就绪。如果函数执行完毕后,此参数的数值是 pdTRUE, 说明有高优先级任务要执行,否则没有。
  • 返回值,如果信号量释放成功返回 pdTRUE,否则返回 errQUEUE_FULL。

注意:

  • 使用此函数前,一定要保证用函数 xSemaphoreCreateBinary()或者 xSemaphoreCreateCounting() 创建了信号量。
  • 此函数不支持使用 xSemaphoreCreateMutex ()创建的信号量。

函数 xSemaphoreTake

xSemaphoreTake( SemaphoreHandle_t xSemaphore, /* 信号量句柄 */
 TickType_t xTicksToWait ); /* 等待信号量可用的最大等待时间 */

函数 xSemaphoreTake 用于在任务代码中获取信号量。

  • 第 1 个参数是信号量句柄。
  • 第 2 个参数是没有信号量可用时,等待信号量可用的最大等待时间,单位系统时钟节拍。
  • 返回值,如果创建成功会获取信号量返回 pdTRUE,否则返回 pdFALSE。

注意:

  • 如果消息队列为空且第 2 个参数为 0,那么此函数会立即返回。
  • 如果用户将 FreeRTOSConfig.h 文件中的宏定义 INCLUDE_vTaskSuspend 配置为 1 且第 2 个参数配 置为 portMAX_DELAY,那么此函数会永久等待直到信号量可用。
`static SemaphoreHandle_t xSemaphore = NULL;  BaseType_t xResult; const TickType_t xMaxBlockTime = pdMS_TO_TICKS(300); /* 设置最大等待时间为 300ms */ xResult = xSemaphoreTake(xSemaphore, (TickType_t)xMaxBlockTime);  if(xResult == pdTRUE) { /* 接收到同步信号 */ printf("接收到同步信号\r\n"); }`

2,二值信号量

FreeRTOS 中二值信号量的源码实现是基于消息队列实现的。

xSemaphoreCreateBinary ()

SemaphoreHandle_t xSemaphoreCreateBinary(void)

函数 xSemaphoreCreateBinary 用于创建二值信号量

xSemaphoreGive ()

xSemaphoreGive( SemaphoreHandle_t xSemaphore ); /* 信号量句柄 */

返回值,如果信号量释放成功返回 pdTRUE,否则返回 pdFALSE,因为信号量的实现是基于消息队 列,返回失败的主要原因是消息队列已经满了。

此函数不支持使用 xSemaphoreCreateRecursiveMutex()创建的信号量。

xSemaphoreGiveFromISR ()

xSemaphoreGiveFromISR
 (
 SemaphoreHandle_t xSemaphore, /* 信号量句柄 */
 signed BaseType_t *pxHigherPriorityTaskWoken /* 高优先级任务是否被唤醒的状态保存 */
 )
  • 第 2 个参数用于保存是否有高优先级任务准备就绪。如果函数执行完毕后,此参数的数值是 pdTRUE, 说明有高优先级任务要执行,否则没有。
  • 返回值,如果信号量释放成功返回 pdTRUE,否则返回 errQUEUE_FULL。
  • 使用此函数前,一定要保证用函数 xSemaphoreCreateBinary()或者 xSemaphoreCreateCounting() 创建了信号量。
  • 此函数不支持使用 xSemaphoreCreateMutex ()创建的信号量。
`BaseType_t xHigherPriorityTaskWoken = pdFALSE; /* 中断消息处理,此处省略 */  …… /* 发送同步信号 */ xSemaphoreGiveFromISR(xSemaphore, &xHigherPriorityTaskWoken); /* 如果 xHigherPriorityTaskWoken = pdTRUE,那么退出中断后切到当前最高优先级任务执行 */ portYIELD_FROM_ISR(xHigherPriorityTaskWoken);`

xSemaphoreTake ()

xSemaphoreTake( SemaphoreHandle_t xSemaphore, /* 信号量句柄 */
 TickType_t xTicksToWait ); /* 等待信号量可用的最大等待时间 */

如果消息队列为空且第 2 个参数为 0,那么此函数会立即返回。

3,互斥信号量

主要作用是对资源实现互斥访问

使用二值信号量也可以实现互斥访问的功能

不过互 斥信号量与二值信号量有区别

`/* 通过二值信号量实现资源互斥访问,永久等待直到资源可用 */ static SemaphoreHandle_t xSemaphore = NULL; const TickType_t xFrequency = 300; /* 获取当前的系统时间 */ xLastWakeTime = xTaskGetTickCount();  xSemaphoreTake(xSemaphore, portMAX_DELAY); printf("任务 vTaskLED 在运行\r\n"); bsp_LedToggle(1); bsp_LedToggle(4); xSemaphoreGive(xSemaphore);  /* vTaskDelayUntil 是绝对延迟,vTaskDelay 是相对延迟。*/  vTaskDelayUntil(&xLastWakeTime, xFrequency);`

互斥信号量可以防止优 先级翻转,而二值信号量不支持

3.1优先级翻转问题

在调度器的作用下,任务 Task3 得到运行,Task3 运行的过程中,由于任务 Task2 就绪,抢占了 Task3的运行。优先级翻转问题就出在这里了,从任务执行的现象上看,任务 Task1 需要等待 Task2 执行完毕才有机会得到执行,这个与抢占式调度正好反了,正常情况下应该是高优先级任务抢占低优先级任务的执行,这里成了高优先级任务 Task1 等待低优先级任务 Task2 完成。所以这种情况被称之为优先级翻转问题。

3.2FreeRTOS 互斥信号量的实现

相对于二值信号量,互斥信号量就是解决了一下优先级翻转的问题

3.3互斥量API

函数 xSemaphoreCreateMutex

SemaphoreHandle_t xSemaphoreCreateMutex( void )

函数 xSemaphoreCreateMutex 用于创建互斥信号量。

返回值,如果创建成功会返回互斥信号量的句柄,如果由于 FreeRTOSConfig.h 文件中 heap 大小不
足,无法为此互斥信号量提供所需的空间会返回 NULL。

#define configUSE_MUTEXES 1

函数 xSemaphoreGive

xSemaphoreGive( SemaphoreHandle_t xSemaphore ); /* 信号量句柄 */

函数 xSemaphoreGive 用于在任务代码中释放信号量。

使用此函数前,一定要保证创建了信号量。

函数 xSemaphoreTake

xSemaphoreTake( SemaphoreHandle_t xSemaphore, /* 信号量句柄 */
TickType_t xTicksToWait ); /* 等待信号量可用的最大等待时间 */

第 2 个参数是没有信号量可用时,等待信号量可用的最大等待时间,单位系统时钟节拍。

如果消息队列为空且第 2 个参数为 0,那么此函数会立即返回。

如果用户将 FreeRTOSConfig.h 文件中的宏定义 INCLUDE_vTaskSuspend 配置为 1 且第 2 个参数配 置为 portMAX_DELAY,那么此函数会永久等待直到信号量可用。

`//练习 static void appobjcreate(void) {     xMutex=xSemphoreMutex();     if(xMutex==NULL)     {              } } static void vTaskTaskUserIF(void *pvParameters) {     uint8_t ucKeycode;     uint8_t pcWriteBuffer[500];     while(1)     {         ucKeycode=bsp_getkey();         if(ucKeycode!=KEY_NONE)         {             switch(ucKeycode)             {                 case KEY_DOWN_K1:                  xSemphoreTake(xMutex,portMAX_DELAY);                  pritf("asdasd");                  xSemphoreGive(xMutex);                  break;                 default:break;             }         }     } }  static void vTaskLED(void *pvParameters) {     TickType_t xLastWakeTime;     const TickType_t xFrequency=200;     xLastWakeTime=xTaskGetTickConut();     while(1)     {         xSemphoreTake(xMutex,portMAX_DELAY);         printf("dasdsad");         xSemphoreGive(xMutex);                  vTaskDealyUntil(&xLastWakeTime,xFrequency);     } }`