下冰雹 · 2020年10月01日

FreeRTOS系列第20篇---FreeRTOS信号量API函数

FreeRTOS的信号量包括二进制信号量、计数信号量、互斥信号量(以后简称互斥量)和递归互斥信号量(以后简称递归互斥量)。我们可以把互斥量和递归互斥量看成特殊的信号量。

信号量API函数实际上都是宏,它使用现有的队列机制。这些宏定义在semphr.h文件中。如果使用信号量或者互斥量,需要包含semphr.h头文件。

二进制信号量、计数信号量和互斥量信号量的创建API函数是独立的,但是获取和释放API函数都是相同的;递归互斥信号量的创建、获取和释放API函数都是独立的。

1创建二进制信号量
1.1函数描述

SemaphoreHandle_t xSemaphoreCreateBinary( void );

这个函数用于创建一个二进制信号量。二进制信号量要么有效要么无效,这也是为什么叫做二进制的原因。

新创建的信号量处于无效状态,这意味着使用API函数xSemaphoreTake()获取信号之前,需要先给出信号。

二进制信号量和互斥量非常相似,但也有细微的区别:互斥量具有优先级继承机制,二进制信号量没有这个机制。这使得二进制信号量更适合用于同步(任务之间或者任务和中断之间),互斥量更适合互锁。

一旦获得二进制信号量后不需要恢复,一个任务或中断不断的产生信号,而另一个任务不断的取走这个信号,通过这样的方式来实现同步。

低优先级任务拥有互斥量的时候,如果另一个高优先级任务也企图获取这个信号量,则低优先级任务的优先级会被临时提高,提高到和高优先级任务相同的优先级。这意味着互斥量必须要释放,否则高优先级任务将不能获取这个互斥量,并且那个拥有互斥量的低优先级任务也永远不会被剥夺,这就是操作系统中的优先级翻转。

互斥量和二进制信号量都是SemaphoreHandle_t类型,并且可以用于任何具有这类参数的API函数中。

1.1.2返回值
  • NULL:创建信号量失败,因为FreeRTOS堆栈不足。
  • 其它值:信号量创建成功。这个返回值存储着信号量句柄。
1.1.3用法举例
SemaphoreHandle_t xSemaphore;
 
void vATask( void * pvParameters )
{
    /* 创建信号量 */
   xSemaphore = xSemaphoreCreateBinary();
 
   if( xSemaphore == NULL )
   {
       /* 因堆栈不足,信号量创建失败,这里进行失败处理*/
   }
   else
   {
       /* 信号量可以使用。信号量句柄存储在变量xSemahore中。
          如果在这里调用API函数xSemahoreTake()来获取信号量,
          则必然是失败的,因为创建的信号量初始是无效(空)的。*/
   }
}
2创建计数信号量
2.1函数描述
SemaphoreHandle_t xSemaphoreCreateCounting ( UBaseType_t uxMaxCount,
                            UBaseType_t uxInitialCount )

创建计数信号量,计数信号量通常用于以下两种情况:

事件计数:在这种应用场合,每当事件发生,事件处理程序会“产生”一个信号量(信号量计数值会递增),每当处理任务处理事件,会取走一个信号量(信号量计数值会递减)。因此,事件发生或者事件被处理后,计数值是会变化的。
资源管理:在这种应用场合下,计数值表示有效资源的数目。为了获得资源,任务首先要获得一个信号量---递减信号量计数值。当计数值为0时,表示没有可用的资源。当占有资源的任务完成,它会释放这个资源,相应的信号量计数值会增一。计数值达到初始值(最大值)表示所有资源都可用。

2.2参数描述
  • uxMaxCount:最大计数值,当信号到达这个值后,就不再增长了。
  • uxInitialCount:创建信号量时的初始值。
2.3返回值

NULL表示信号量创建失败,否则返回信号量句柄

2.4用法举例
void vATask( void * pvParameters )
 {
     xSemaphoreHandle xSemaphore;
 
     // 必须先创建信号量,才能使用它
     // 信号量可以计数的最大值为10,计数初始值为0.
     xSemaphore = xSemaphoreCreateCounting( 10, 0 );
 
     if( xSemaphore != NULL )
     {
         // 信号量创建成功
         // 现在可以使用信号量了。
     }
 }
3创建互斥量
3.1函数描述

SemaphoreHandle_t xSemaphoreCreateMutex( void )

创建互斥量。可以使用API函数xSemaphoreTake()和xSemaphoreGive()访问互斥量,但是绝不可以用xSemaphoreTakeRecursive()和xSemaphoreGiveRecursive()访问。

二进制信号量和互斥量非常相似,但也有细微的区别:互斥量具有优先级继承机制,二进制信号量没有这个机制。这使得二进制信号量更适合用于同步(任务之间或者任务和中断之间),互斥量更适合互锁。

一旦获得二进制信号量后不需要恢复,一个任务或中断不断的产生信号,而另一个任务不断的取走这个信号,通过这样的方式来实现同步。

低优先级任务拥有互斥量的时候,如果另一个高优先级任务也企图获取这个信号量,则低优先级任务的优先级会被临时提高,提高到和高优先级任务相同的优先级。这意味着互斥量必须要释放,否则高优先级任务将不能获取这个互斥量,并且那个拥有互斥量的低优先级任务也永远不会被剥夺,这就是操作系统中的优先级翻转。

互斥量和二进制信号量都是SemaphoreHandle_t类型,并且可以用于任何具有这类参数的API函数中。

3.2返回值

NULL表示信号量创建失败,否则返回信号量句柄。

3.3用法举例
xSemaphoreHandle xSemaphore;
 
voidvATask( void * pvParameters )
{
    // 互斥量在未创建之前是不可用的
    xSemaphore = xSemaphoreCreateMutex();
    if( xSemaphore != NULL )
    {
        // 创建成功
        // 在这里可以使用这个互斥量了 
    }
}
4创建递归互斥量
4.1函数描述

SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void )

用于创建递归互斥量。被创建的互斥量可以被API函数xSemaphoreTakeRecursive()和xSemaphoreGiveRecursive()使用,但不可以被API函数xSemaphoreTake()和xSemaphoreGive()使用。

递归类型的互斥量可以被拥有者重复获取。拥有互斥量的任务必须调用API函数xSemaphoreGiveRecursive()将拥有的递归互斥量全部释放后,该信号量才真正被释放。比如,一个任务成功获取同一个互斥量5次,那么这个任务要将这个互斥量释放5次之后,其它任务才能获取到它。

递归互斥量具有优先级继承机制,因此任务获得一次信号后必须在使用完后做一个释放操作。

互斥量类型信号不可以用在中断服务例程中。

4.2返回值

   NULL表示互斥量创建失败,否则返回互斥量句柄。

4.3用法举例

xSemaphoreHandle xMutex;
 
void vATask( void * pvParameters )
{
    // 互斥量未创建前是不能被使用的
    xMutex = xSemaphoreCreateRecursiveMutex();
 
    if( xMutex != NULL )
    {
        // 创建成功
        // 在这里创建互斥量
    }
}
5删除信号量
5.1函数描述

void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );

删除信号量。如果有任务阻塞在这个信号量上,则这个信号量不要删除。

5.2参数描述

xSemaphore:信号量句柄

6获取信号量
6.1函数描述

xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait)

获取信号量。信号量必须是通过API函数xSemaphoreCreateBinary()、xSemaphoreCreateCounting()和xSemaphoreCreateMutex()预先创建过的。注意,递归互斥量类型信号量不能使用该函数、不用在中断服务程序中使用该函数。

6.2参数描述
  • xSemaphore:信号量句柄
  • xTickToWait:信号量无效时,任务最多等待的时间,单位是系统节拍周期个数。使用宏portTICK_PERIOD_MS可以辅助将系统节拍个数转化为实际时间(以毫秒为单位)。如果设置为0,表示不是设置等待时间。如果INCLUDE_vTaskSuspend设置为1,并且参数xTickToWait为portMAX_DELAY则可以无限等待。
6.3返回值

成功获取到信号量返回pdTRUE,否则返回pdFALSE。

6.4用法举例
SemaphoreHandle_t xSemaphore = NULL;
 
/*这个任务创建信号量 */
void vATask( void * pvParameters )
{
    /*创建互斥型信号量,用于保护共享资源。*/
    xSemaphore = xSemaphoreCreateMutex();
}
 
/* 这个任务使用信号量 */
void vAnotherTask( void * pvParameters )
{
    /* ... 做其它事情. */
 
    if( xSemaphore != NULL )
    {
        /*如果信号量无效,则最多等待10个系统节拍周期。*/
        if( xSemaphoreTake( xSemaphore, ( TickType_t ) 10 ) == pdTRUE )
        {
            /*到这里我们获取到信号量,现在可以访问共享资源了*/
 
            /* ... */
 
            /* 完成访问共享资源后,必须释放信号量*/
            xSemaphoreGive( xSemaphore );
        }
        else
        {
            /* 没有获取到信号量,这里处理异常情况。*/
        }
    }
}
7获取信号量(带中断保护)
7.1函数描述
 xSemaphoreTakeFromISR(SemaphoreHandle_t xSemaphore,
                         signedBaseType_t *pxHigherPriorityTaskWoken)

API函数xSemaphoreTake()的另一版本,用于中断服务程序。

7.2参数描述
  • xSemaphore:信号量句柄
  • pxHigherPriorityTaskWoken:如果*pxHigherPriorityTaskWoken为pdTRUE,则需要在中断退出前手动进行一次上下文切换。从FreeRTOS V7.3.0开始,该参数为可选参数,并可以设置为NULL。
7.3返回值

信号量成功获取返回pdTRUE,否则返回pdFALSE。

8获取递归互斥量
8.1函数描述

xSemaphoreTakeRecursive(SemaphoreHandle_t xMutex, TickType_t xTicksToWait );

获取递归互斥信号量。互斥量必须是通过API函数xSemaphoreCreateRecursiveMutex()创建的类型。

文件FreeRTOSConfig.h中的宏configUSE_RECURSIVE_MUTEXES必须设置成1,此函数才有效。

已经获取递归互斥量的任务可以重复获取该递归互斥量。使用xSemaphoreTakeRecursive() 函数成功获取几次递归互斥量,就要使用xSemaphoreGiveRecursive()函数返还几次,在此之前递归互斥量都处于无效状态。比如,某个任务成功获取5次递归互斥量,那么在它没有返还5次该递归互斥量之前,这个互斥量对别的任务无效。

8.2参数描述
  • xMutex:互斥量句柄,必须是使用API函数xSemaphoreCreateRecursiveMutex()返回的。
  • xTickToWait:互斥量无效时,任务最多等待的时间,单位是系统节拍周期个数。使用宏portTICK_PERIOD_MS可以辅助将系统节拍个数转化为实际时间(以毫秒为单位)。如果设置为0,表示不是设置等待时间。如果任务已经拥有信号量则xSemaphoreTakeRecursive()立即返回,不管xTickToWait是什么值。
8.3返回值

成功获取递归互斥量返回pdTURE,否则返回pdFALSE。

8.4用法举例
SemaphoreHandle_t xMutex = NULL;
 
// 这个任务创建互斥量
void vATask( void *pvParameters )
{
    // 这个互斥量用于保护共享资源
    xMutex =xSemaphoreCreateRecursiveMutex();
}
 
//这个任务使用互斥量
void vAnotherTask( void *pvParameters )
{
    // ... 做其它事情.
 
    if( xMutex != NULL )
    {
        // 如果互斥量无效,则最多等待10系统时钟节拍周期.   
        if(xSemaphoreTakeRecursive( xMutex, ( TickType_t ) 10 ) == pdTRUE )
        {
            // 到这里我们成功获取互斥量并可以访问共享资源了
 
            // ...
            // 由于某种原因,某些代码需要在一个任务中多次调用API函数
            // xSemaphoreTakeRecursive()。当然不会像本例中这样连续式
            //调用,实际代码会有更加复杂的结构
            xSemaphoreTakeRecursive( xMutex, ( TickType_t ) 10 );
            xSemaphoreTakeRecursive( xMutex, ( TickType_t ) 10 );
 
            // 我们获取一个互斥量三次,所以我们要将这个互斥量释放三次
            //它才会变得有效。再一次说明,实际代码可能会更加复杂。
            xSemaphoreGiveRecursive( xMutex );
            xSemaphoreGiveRecursive( xMutex );
            xSemaphoreGiveRecursive( xMutex );
 
            // 到这里,这个共享资源可以被其它任务使用了.
        }
        else
        {
            // 处理异常情况
        }
    }
}
9释放信号量
9.1函数描述

xSemaphoreGive(SemaphoreHandle_t xSemaphore )

用于释放一个信号量。信号量必须是API函数xSemaphoreCreateBinary()、xSemaphoreCreateCounting()或xSemaphoreCreateMutex() 创建的。必须使用API函数xSemaphoreTake()获取这个信号量。

这个函数绝不可以在中断服务例程中使用,可以使用带中断保护版本的API函数xSemaphoreGiveFromISR()来实现相同功能。

这个函数不能用于使用API函数xSemaphoreCreateRecursiveMutex()所创建的递归互斥量。

9.2参数描述

xSemaphore:信号量句柄。

9.3返回值

信号量释放成功返回pdTRUE,否则返回pdFALSE。

9.4用法举例
SemaphoreHandle_t xSemaphore = NULL;
 
voidvATask( void * pvParameters )
{
   // 创建一个互斥量,用来保护共享资源
   xSemaphore = xSemaphoreCreateMutex();
 
    if( xSemaphore != NULL )
    {
         if( xSemaphoreGive( xSemaphore ) != pdTRUE )
         {
              //我们希望这个函数调用失败,因为首先要获取互斥量
         }
         // 获取信号量,不等待
         if( xSemaphoreTake( xSemaphore, ( TickType_t ) 0 ) )
         {
              // 现在我们拥有互斥量,可以安全的访问共享资源
              if( xSemaphoreGive( xSemaphore ) != pdTRUE )
              {
                   //我们不希望这个函数调用失败,因为我们必须
                   //要释放已获取的互斥量
              }
         }
     }
}
10释放信号量(带中断保护)
10.1函数描述
xSemaphoreGiveFromISR(SemaphoreHandle_t xSemaphore,
                         signed BaseType_t *pxHigherPriorityTaskWoken )

释放信号量。是API函数xSemaphoreGive()的另个版本,用于中断服务程序。信号量必须是通过API函数xSemaphoreCreateBinary()或xSemaphoreCreateCounting()创建的。这里没有互斥量,是因为互斥量不可以用在中断服务程序中。

10.2参数描述
  • xSemaphore:信号量句柄
  • pxHigherPriorityTaskWoken:如果*pxHigherPriorityTaskWoken为pdTRUE,则需要在中断退出前人为的经行一次上下文切换。从FreeRTOS V7.3.0开始,该参数为可选参数,并可以设置为NULL。
10.3返回值

成功释放信号量返回pdTURE,否则返回errQUEUE_FULL。

10.4用法举例
#define LONG_TIME 0xffff
#define TICKS_TO_WAIT    10
 
SemaphoreHandle_t xSemaphore = NULL;
 
/* Repetitive task. */
void vATask( void * pvParameters )
{
    /* 我们使用信号量同步,所以先创建一个二进制信号量.必须确保
       在创建这个二进制信号量之前,中断不会访问它。*/
    xSemaphore = xSemaphoreCreateBinary();
 
    for( ;; )
    {
        /* 我们希望每产生10次定时器中断,任务运行一次。*/
        if( xSemaphoreTake( xSemaphore, LONG_TIME ) == pdTRUE )
        {
            /* 到这里成功获取到信号量*/
 
            ...
 
           /* 我们成功执行完一次,由于这是个死循环,所以任务仍会
               阻塞在等待信号量上。信号量由ISR释放。*/
        }
    }
}
 
/* 定时器 ISR */
void vTimerISR( void * pvParameters )
{
    static unsigned char ucLocalTickCount = 0;
    static signed BaseType_txHigherPriorityTaskWoken;
 
    /*定时器中断发生 */
 
      ...执行其它代码
 
    /*需要vATask() 运行吗? */
    xHigherPriorityTaskWoken = pdFALSE;
    ucLocalTickCount++;
    if( ucLocalTickCount >= TICKS_TO_WAIT )
    {
        /* 释放信号量,解除vATask任务阻塞状态 */
        xSemaphoreGiveFromISR( xSemaphore, &xHigherPriorityTaskWoken );
 
        /* 复位计数器 */
        ucLocalTickCount = 0;
    }
 
    /* 如果 xHigherPriorityTaskWoken 表达式为真,需要执行一次上下文切换*/
    portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
11释放递归互斥量
11.1函数描述

xSemaphoreGiveRecursive(SemaphoreHandle_t xMutex )

释放一个递归互斥量。互斥量必须是使用 API函数xSemaphoreCreateRecursiveMutex()创建的。文件FreeRTOSConfig.h中宏configUSE_RECURSIVE_MUTEXES必须设置成1本函数才有效。

11.2参数描述
  • xMutex:互斥量句柄。必须是函数xSemaphoreCreateRecursiveMutex()返回的值。
11.3返回值

如果递归互斥量释放成功,返回pdTRUE。

11.4用法举例

见“8 获取递归互斥量”。

12获取互斥量持有任务的句柄
12.1函数描述
TaskHandle_t xSemaphoreGetMutexHolder( SemaphoreHandle_t xMutex );

返回互斥量持有任务的句柄(如果有的话),互斥量由参数xMutex指定。

如果调用此函数的任务持有互斥量,那么可以可靠的返回任务句柄,但是如果是别的任务持有互斥量,则不总可靠。

文件FreeRTOSConfig.h中宏configUSE_MUTEXES必须设置成1本函数才有效。

12.2参数描述
  • xMutex:互斥量句柄
12.3返回值

返回互斥量持有任务的句柄。如果参数xMutex不是互斥类型信号量或者虽然互斥量有效但这个互斥量不被任何任务持有则返回NULL。

这是FreeRTOS基础篇的最后一篇博文,到这里我们已经可以移植、熟练使用FreeRTOS了。但如果想知道FreeRTOS背后的运行机制,这些是远远不够的,下面要走的路还会很长。要不要了解FreeRTOS背后运行机制,全凭各位的兴趣,毕竟我们即使不清楚汽车的构造细节,但只要掌握驾驶技巧也可以很好的开车的。使用RTOS也与之相似,只要我们掌握了基础篇的那些知识,我们已经可以很好的使用FreeRTOS了。探索FreeRTOS背后运行的机制,是我们对未知事件的好奇,也是我们相信理解了FreeRTOS运行机制,可以让我们更优雅、更少犯错、更举重若轻的的使用RTOS。
FreeRTOS高级篇已经开始写了,可以点击这里擦看最新的文章列表。

相关阅读

FreeRTOS系列第18篇---FreeRTOS队列API函数
FreeRTOS系列第19篇---FreeRTOS信号量

作者:朱工
首发博客:https://blog.csdn.net/zhzht19861011/article/details/50920336
关注FreeRTOS从基础到高级专栏,即时收取FreeRTOS系列文章。
推荐阅读
关注数
3263
内容数
54
介绍FreeRTOS的基本功能,移植与使用。主要介绍FreeRTOS的裁剪、任务、内存管理、队列、信号量、任务通知等基本组成,看完可以会用FreeRTOS,高级篇会深入介绍FreeRTOS的实现细节、方法、技巧。
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息