FreeRTOS系列第24篇---FreeRTOS信号量分析

李肖遥 2021-07-01 08:38


关注、星标公众号,直达精彩内容

ID:技术让梦想更伟大

整理:李肖遥


FreeRTOS的信号量包括二进制信号量、计数信号量、互斥信号量(以后简称互斥量)和递归互斥信号量(以后简称递归互斥量)。

关于它们的区别可以参考《 FreeRTOS系列第19篇---FreeRTOS信号量》一文。

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

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

递归互斥信号量的创建、获取和释放API函数都是独立的。

1.信号量创建

在《FreeRTOS高级篇5---FreeRTOS队列分析》中,我们分析了队列的实现过程,包括队列创建、入队和出队操作。

在那篇文章中我们说过,创建队列API函数实际是调用通用队列创建函数xQueueGenericCreate()来实现的。

其实,不但创建队列实际调用通用队列创建函数,二进制信号量、计数信号量、互斥量和递归互斥量也都直接或间接使用这个函数,如表1-1所示。

表1-1中红色字体表示是间接调用xQueueGenericCreate()函数。

表1-1:队列、信号量和互斥量创建宏与直接(间接)执行函数

1.1.创建二进制信号量

二进制信号量创建实际上是直接使用通用队列创建函数xQueueGenericCreate()。创建二进制信号量API接口实际上是一个宏,定义如下:

#define xSemaphoreCreateBinary()         \
   xQueueGenericCreate(              \
            ( UBaseType_t ) 1,       \
            semSEMAPHORE_QUEUE_ITEM_LENGTH,  \
            NULL,              \
            NULL,              \
            queueQUEUE_TYPE_BINARY_SEMAPHORE\
            )

通过这个宏定义我们知道创建二进制信号量实际上是创建了一个队列,队列项有1个,但是队列项的大小为0(宏semSEMAPHORE_QUEUE_ITEM_LENGTH定义为0)。

有了队列创建的知识,我们可以很容易的画出初始化后的二进制信号量内存,如图1-1所示。

图1-1:初始化后的二进制信号量对象内存

或许不止一人像我一样奇怪,创建一个没有队列项存储空间的队列,「信号量用什么表示?」

其实二进制信号量的释放和获取都是通过操作队列结构体成员uxMessageWaiting来实现的(图1-1红色部分,uxMessageWaiting表示队列中当前队列项的个数)。

经过初始化后,变量uxMessageWaiting为0,这说明队列为空,也就是信号量处于无效状态。

在使用API函数xSemaphoreTake()获取信号之前,需要先释放一个信号量。后面讲到二进制信号量释放和获取时还会详细介绍。

1.2.创建计数信号量

创建计数信号量间接使用通用队列创建函数xQueueGenericCreate()。创建计数信号量API接口同样是个宏定义:

#define xSemaphoreCreateCounting(uxMaxCount, uxInitialCount )             \
       xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ), (NULL ) )

创建计数信号量API接口有两个参数,含义如下:

  • 「uxMaxCount」:最大计数值,当信号到达这个值后,就不再增长了。

  • 「uxInitialCount」:创建信号量时的初始值。

我们来看一下函数xQueueCreateCountingSemaphore()如何实现的:

QueueHandle_t xQueueCreateCountingSemaphore( const UBaseType_tuxMaxCount, const UBaseType_t uxInitialCount, StaticQueue_t *pxStaticQueue )
{
QueueHandle_t xHandle;
 
  configASSERT( uxMaxCount != 0 );
  configASSERT( uxInitialCount <= uxMaxCount );

  /*调用通用队列创建函数*/
  xHandle =xQueueGenericCreate(
        uxMaxCount,
        queueSEMAPHORE_QUEUE_ITEM_LENGTH,
        NULL,
        pxStaticQueue,
        queueQUEUE_TYPE_COUNTING_SEMAPHORE );

  if( xHandle != NULL )
  {
      ( ( Queue_t * ) xHandle )->uxMessagesWaiting = uxInitialCount;
  }
  configASSERT( xHandle );
  return xHandle;
}

从代码可以看出,创建计数信号量仍然调用通用队列创建函数xQueueGenericCreate()来创建一个队列,队列项的数目由参数uxMaxCount指定,每个队列项的大小由宏queueSEMAPHORE_QUEUE_ITEM_LENGTH指出,我们找到这个宏定义发现,这个宏被定义为0,也就是说创建的队列只有队列数据结构存储空间而没有队列项存储空间。

如果队列创建成功,则将队列结构体成员uxMessageWaiting设置为初始计数信号量值。初始化后的计数信号量内存如图1-2所示。

图1-2:初始化后的计数信号量对象内存

1.3创建互斥量

创建互斥量间接使用通用队列创建函数xQueueGenericCreate()。创建互斥量API接口同样是个宏,定义如下:

#define xSemaphoreCreateMutex()             \
          xQueueCreateMutex( queueQUEUE_TYPE_MUTEX, NULL )

其中,宏queueQUEUE_TYPE_MUTEX用于通用队列创建函数,表示创建队列的类型是互斥量,在文章《FreeRTOS高级篇5---FreeRTOS队列分析》关于通用队列创建函数参数说明中提到了这个宏。

我们来看一下函数xQueueCreateMutex()是如何实现的:

#if ( configUSE_MUTEXES == 1 )
    QueueHandle_t xQueueCreateMutex( const uint8_tucQueueType, StaticQueue_t *pxStaticQueue )
    {
    Queue_t *pxNewQueue;
    const UBaseType_tuxMutexLength = ( UBaseType_t ) 1, uxMutexSize = ( UBaseType_t ) 0;
 
        /* 防止编译器产生警告信息 */
        ( void ) ucQueueType;
       
        /*调用通用队列创建函数*/
        pxNewQueue = ( Queue_t * )xQueueGenericCreate( uxMutexLength, uxMutexSize, NULL, pxStaticQueue, ucQueueType );
 
        /* 成功分配新的队列结构体? */
        if( pxNewQueue != NULL )
        {
            /*xQueueGenericCreate()函数会按照通用队列的方式设置所有队列结构体成员,但是我们是要创建互斥量.因此需要对一些结构体成员重新赋值. */
            pxNewQueue->pxMutexHolder = NULL;
          
            pxNewQueue->uxQueueType =queueQUEUE_IS_MUTEX;  //NULL
 
            /* 用于递归互斥量创建 */
            pxNewQueue->u.uxRecursiveCallCount = 0;
 
            /* 使用一个预期状态启动信号量 */
            ( void ) xQueueGenericSend( pxNewQueue, NULL, ( TickType_t ) 0U, queueSEND_TO_BACK);
        }
 
        return pxNewQueue;
    }
#endif /* configUSE_MUTEXES */

这个函数是带条件编译的,只有将宏configUSE_MUTEXES定义为1才会编译这个函数。

函数首先调用通用队列创建函数xQueueGenericCreate()来创建一个队列,队列项数目为1,队列项大小为0,说明创建的队列只有队列数据结构存储空间而没有队列项存储空间。

如果队列创建成功,通用队列创建函数还会按照通用队列的方式 初始化所有队列结构体成员。

但是这里要创建的是互斥量,所以有一些结构体成员必须重新赋值。

在这段代码中,可能你会疑惑,队列结构体成员中,并没有pxMutexHolderuxQueueType

其实这两个标识符只是宏定义,是专门为互斥量而定义的,如下所示:

#define pxMutexHolder              pcTail
#define uxQueueType                pcHead
#define queueQUEUE_IS_MUTEX        NULL

当队列结构体用于互斥量时,成员pcHead和pcTail指针就不再需要,并且将pcHead指针设置为NULL,表示pcTail指针实际指向互斥量持有者任务TCB(如果有的话)。

最后调用函数xQueueGenericSend()释放一个互斥量,相当于互斥量创建后是有效的,可以直接使用获取信号量API函数来获取这个互斥量。

如果某资源同时只准一个任务访问,可以用互斥量保护这个资源。

这个资源一定是存在的,所以创建互斥量时会先释放一个互斥量,表示这个资源可以使用。

任务想访问资源时,先获取互斥量,等使用完资源后,再释放它。

也就是说互斥量一旦创建好后,要先获取,后释放,要在同一个任务中获取和释放。

这也是互斥量和二进制信号量的一个重要区别,二进制信号量可以在随便一个任务中获取或释放,然后也可以在任意一个任务中释放或获取。

「互斥量不同于二进制信号量的还有」:互斥量具有优先级继承机制,二进制信号量没有,互斥量不可以用于中断服务程序,二进制信号量可以。

初始化后的互斥量内存如图1-3所示。

图1-3:初始化后的互斥量对象内存

1.4创建递归互斥量

创建递归互斥量间接使用通用队列创建函数xQueueGenericCreate()。创建递归互斥量API接口同样是个宏,定义如下:

#definexSemaphoreCreateRecursiveMutex()                 \
          xQueueCreateMutex(queueQUEUE_TYPE_RECURSIVE_MUTEX, NULL )

其中,宏queueQUEUE_TYPE_RECURSIVE_MUTEX用于通用队列创建函数,表示创建队列的类型是递归互斥量,在文章《FreeRTOS高级篇5---FreeRTOS队列分析》关于通用队列创建函数参数说明中提到了这个宏。

创建互斥量和创建递归互斥量是调用的同一个函数xQueueCreateMutex();

至于参数queueQUEUE_TYPE_RECURSIVE_MUTEX,我们在FreeRTOS一文中已经知道,它只是用于可视化调试;

因此创建互斥量和创建递归互斥量可以看作是一样的,初始化后的递归互斥量对象内存也和互斥量一样,如图1-3所示。

2.释放信号量

无论二进制信号量、计数信号量还是互斥量,它们都使用相同的获取和释放API函数。释放信号量用于使信号量有效,分为不带中断保护和带中断保护两个版本。

2.1 xSemaphoreGive()

用于释放一个信号量,不带中断保护。被释放的信号量可以是二进制信号量、计数信号量和互斥量。

注意递归互斥量并不能使用这个API函数释放。其实信号量释放是一个宏,真正调用的函数是xQueueGenericSend(),宏定义如下:

#definexSemaphoreGive( xSemaphore )                    \
              xQueueGenericSend(                       \
                     ( QueueHandle_t ) ( xSemaphore ), \
                     NULL,                \
                     semGIVE_BLOCK_TIME,  \
                     queueSEND_TO_BACK )

可以看出释放信号量实际上是一次入队操作,并且阻塞时间为0(由宏semGIVE_BLOCK_TIME定义)。

对于二进制信号量和计数信号量,根据上一章的内容可以总结出,释放一个信号量的过程实际上可以简化为两种情况:

「第一」,如果队列未满,队列结构体成员uxMessageWaiting加1,判断是否有阻塞的任务,有的话解除阻塞,然后返回成功信息(pdPASS);

「第二」,如果队列满,返回错误代码(err_QUEUE_FULL),表示队列满。

对于互斥量要复杂些,因为互斥量具有优先级继承机制。

「优先级继承是个什么过程呢?」

我们举个例子。某个资源X同时只能有一个任务访问,现在有任务A和任务C都要访问这个资源,任务A的优先级为1,任务C的优先级为10,所以任务C的优先级大于任务A的优先级。

我们用互斥量保护资源X,并且当前任务A正在访问资源X。

在任务A访问资源X的过程中,来了一个中断,中断事件使得任务C执行。

任务C执行的过程中,也想访问资源X,但是因为资源X还被任务A独占着,所以任务C无法获取互斥量,会进入阻塞状态。

此时,低优先级任务A会继承高优先级任务C的优先级,任务A的优先级临时的被提升,优先级变成10。这个机制能够将已经发生的优先级反转影响降低到最小。

「那么什么是优先级反转呢?」

还是看上面的例子,任务C的优先级高于任务A,但是任务C因为没有获得互斥量而进入阻塞,只能等待低优先级的任务A释放互斥量后才能运行,这种情况就是优先级反转。

「那为什么优先级继承可以降低优先级反转的影响呢?」

还是看上面的例子,不过我们再增加一个优先级为5的任务B,这三个任务都处于就绪状态。

如果没有优先级继承机制,三个任务的优先级顺序为任务C>任务B>任务A。

当任务C因为得不到互斥量而阻塞后,任务B会获取CPU权限,等到任务B主动或被动让出CPU后,任务A才会执行,任务A释放互斥量后,任务C才能得到运行。

再看一下有优先级继承的情况,当任务C因为得不到互斥量而阻塞后,任务A继承任务C的优先级,现在三个任务的优先级顺序为任务C=任务A>任务B。

当任务C因为得不到互斥量而阻塞后,任务A会获得CPU权限,等到任务A释放互斥量后,任务C就会得到运行。看,任务C等待的时间变短了。

有了上面的基础理论,我们就很好理解为什么释放互斥量会比较复杂了。

「还是可以简化为两种情况:」

「第一」,如果队列未满,除了队列结构体成员uxMessageWaiting加1外,还要判断获取互斥量的任务是否有优先级继承,如果有的话,还要将任务的优先级恢复到原始值。当然,恢复到原来值也是有条件的,就是该任务必须在没有使用其它互斥量的情况下,才能将继承的优先级恢复到原始值。然后判断是否有阻塞的任务,有的话解除阻塞,最后返回成功信息(pdPASS);

「第二」,如果如果队列满,返回错误代码(err_QUEUE_FULL),表示队列满。

2.2xSemaphoreGiveFromISR()

用于释放一个信号量,带中断保护。被释放的信号量可以是二进制信号量和计数信号量。

和普通版本的释放信号量API函数不同,它不能释放互斥量,这是因为互斥量不可以在中断中使用!

互斥量的优先级继承机制只能在任务中起作用,在中断中毫无意义。带中断保护的信号量释放其实也是一个宏,真正调用的函数是xQueueGiveFromISR (),宏定义如下:

#definexSemaphoreGiveFromISR( xSemaphore, pxHigherPriorityTaskWoken )     \
            xQueueGiveFromISR(                     \
                ( QueueHandle_t ) ( xSemaphore),  \
                ( pxHigherPriorityTaskWoken ) )

我们看真正被调用的函数源码(经过整理后的):

BaseType_t xQueueGiveFromISR(
            QueueHandle_t xQueue,
            BaseType_t * constpxHigherPriorityTaskWoken )
{
BaseType_t xReturn;
UBaseType_t uxSavedInterruptStatus;
Queue_t * const pxQueue = ( Queue_t * ) xQueue;
uxSavedInterruptStatus =portSET_INTERRUPT_MASK_FROM_ISR();
  {
    /*当队列用于实现信号量时,永远不会有数据出入队列,但是任然要检查队列是否为空 */
    if( pxQueue->uxMessagesWaiting < pxQueue->uxLength )
    {
      /* 一个任务可以获取多个互斥量,但是只能有一个继承优先级,如果任务是互斥量的持有者,则互斥量不允许在中断服务程序中释放.因此这里不需要判断是否要恢复任务的原始优先级值,只是简单更新队列项计数器. */
      ++( pxQueue->uxMessagesWaiting );

      /* 如果列表上锁,不能改变队列的事件列表. */
      if( pxQueue->xTxLock == queueUNLOCKED )
      {
        if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive) ) == pdFALSE )
        {
          if(xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive) ) != pdFALSE )
          {
            /* 解除阻塞的任务有更高优先级,因此记录上下文切换请求*/
            if(pxHigherPriorityTaskWoken != NULL )
            {
              *pxHigherPriorityTaskWoken= pdTRUE;
            }
          }
        }
      }
      else
      {
        /* Increment thelock count so the task that unlocks the queue
        knows that data wasposted while it was locked. */
        ++( pxQueue->xTxLock );
      }

      xReturn = pdPASS;
    }
    else
    {
      xReturn = errQUEUE_FULL;
    }
  }
  portCLEAR_INTERRUPT_MASK_FROM_ISR(uxSavedInterruptStatus );

  return xReturn;
}

因为不涉及互斥量,不涉及阻塞,函数xQueueGiveFromISR()异常简单,如果队列满,直接返回错误代码(err_QUEUE_FULL);

如果队列未满,则将队列结构体成员uxMessageWaiting加1,然后视队列是否上锁而决定是否解除任务阻塞(如果有得话)。

如果你觉得难以理解,则需要先看看《FreeRTOS高级篇5---FreeRTOS队列分析》。

3.获取信号量

无论二进制信号量、计数信号量还是互斥量,它们都使用相同的获取和释放API函数。

释获取信号量会消耗信号量,如果获取信号量失败,任务可能会阻塞,阻塞时间由函数参数xBlockTime指定,如果为0,则直接返回,不阻塞。

获取信号量分为不带中断保护和带中断保护两个版本。

3.1 xSemaphoreTake

用于获取信号量,不带中断保护。获取的信号量可以是二进制信号量、计数信号量和互斥量。

注意递归互斥量并不能使用这个API函数获取。其实获取信号量是一个宏,真正调用的函数是xQueueGenericReceive (),宏定义如下:

#definexSemaphoreTake( xSemaphore, xBlockTime )        \
              xQueueGenericReceive(                    \
             ( QueueHandle_t ) ( xSemaphore ),         \
              NULL,                                    \
             ( xBlockTime ),                           \
              pdFALSE )

通过上面的宏定义可以看出,获取信号量实际上是执行出队操作。

「对于二进制信号量和计数信号量,可以简化为三种情况:」

「第一」,如果队列不为空,队列结构体成员uxMessageWaiting减1,判断是否有因入队而阻塞的任务,有的话解除阻塞,然后返回成功信息(pdPASS);

「第二」,如果队列为空并且阻塞时间为0,则直接返回错误码(errQUEUE_EMPTY),表示队列为空;

「第三」,如果队列为空并且阻塞时间不为0,则任务会因为等待信号量而进入阻塞状态,任务会被挂接到延时列表中。

「对于互斥量,也可以简化为三种情况」,但是过程要复杂一些:

「第一」,如果队列不为空,队列结构体成员uxMessageWaiting减1、将当前任务TCB结构体成员uxMutexesHeld加1,表示任务获取互斥量的个数、将队列结构体成员指针pxMutexHolder指向任务TCB、判断是否有因入队而阻塞的任务,有的话解除阻塞,然后返回成功信息(pdPASS);

「第二」,如果队列为空并且阻塞时间为0,则直接返回错误码(errQUEUE_EMPTY),表示队列为空;

「第三」,如果队列为空并且阻塞时间不为0,则任务会因为等待信号量而进入阻塞状态,在将任务挂接到延时列表之前,会判断当前任务和拥有互斥量的任务优先级哪个高,如果当前任务优先级高,则拥有互斥量的任务继承当前任务优先级。

3.2xSemaphoreTakeFromISR()

用于获取信号量,带中断保护。获取的信号量可以是二进制信号量和计数信号量。

和普通版本的获取信号量API函数不同,它不能获取互斥量,这是因为互斥量不可以在中断中使用!

互斥量的优先级继承机制只能在任务中起作用,在中断中毫无意义。

带中断保护的获取信号量其实也是一个宏,真正调用的函数是xQueueReceiveFromISR (),宏定义如下:

#definexSemaphoreTakeFromISR( xSemaphore, pxHigherPriorityTaskWoken )   \
              xQueueReceiveFromISR(              \
             ( QueueHandle_t ) ( xSemaphore ),   \
              NULL,                              \
             ( pxHigherPriorityTaskWoken ) )

同样因为不涉及互斥量,不涉及阻塞,函数xQueueReceiveFromISR ()同样异常简单:如果队列为空,直接返回错误代码(pdFAIL);

如果队列非空,则将队列结构体成员uxMessageWaiting减1,然后视队列是否上锁而决定是否解除任务阻塞(如果有得话)。

4.释放递归互斥量

函数xSemaphoreGiveRecursive()用于释放一个递归互斥量。

已经获取递归互斥量的任务可以重复获取该递归互斥量。

使用xSemaphoreTakeRecursive() 函数成功获取几次递归互斥量,就要使用xSemaphoreGiveRecursive()函数返还几次,在此之前递归互斥量都处于无效状态。

比如,某个任务成功获取5次递归互斥量,那么在它没有返还5次该递归互斥量之前,这个互斥量对别的任务无效。

像其它信号量一样,xSemaphoreGiveRecursive()也是一个宏定义,它最终使用现有的队列机制,实际执行的函数是xQueueGiveMutexRecursive(),这个宏定义如下所示:

#definexSemaphoreGiveRecursive( xMutex )            \
                xQueueGiveMutexRecursive( (xMutex ) )  

我们重点来看函数xQueueGiveMutexRecursive()的实现过程。经过整理后(去除跟踪调试语句)的源码如下所示:

#if ( configUSE_RECURSIVE_MUTEXES == 1 )
    BaseType_txQueueGiveMutexRecursive( QueueHandle_t xMutex )
    {
    BaseType_t xReturn;
    Queue_t * const pxMutex = ( Queue_t * ) xMutex;

   /* 互斥量和递归互斥量要在同一个任务中获取和释放,当获取互斥量或递归互斥量时,队列结构体成员指针pxMutexHolder指向获取互斥量或递归互斥量的任务TCB,所以在释放递归互斥量时需要检查这个指针指向的TCB是否是和当前任务TCB相同,如果不相同是不能释放这个递归互斥量的! 注:释放互斥量时,这个检查不是必须的,FreeRTOS的作者将这个检查放在了断言中(configASSERT( pxTCB == pxCurrentTCB);).*/
        if( pxMutex->pxMutexHolder == ( void * )xTaskGetCurrentTaskHandle() )
        {
            /* 每当任务获取递归互斥量时,队列结构体成员u.uxRecursiveCallCount会加1,互斥量不会使用这个变量,它用来保存递归次数.所以,在释放递归互斥量的时候要将它减1*/
            ( pxMutex->u.uxRecursiveCallCount)--;
 
            /* 递归计数器为0? */
            if( pxMutex->u.uxRecursiveCallCount == ( UBaseType_t ) 0 )
            {
                /* 调用入队函数释放一个互斥量,注意阻塞时间(由宏queueMUTEX_GIVE_BLOCK_TIME定义)为0 */
                ( void ) xQueueGenericSend( pxMutex, NULL,queueMUTEX_GIVE_BLOCK_TIME, queueSEND_TO_BACK );
            }
 
            xReturn = pdPASS;
        }
        else
        {
            /* 如果不是本任务拥有这个互斥量,则直接返回错误码 */
            xReturn = pdFAIL;
        }
 
        return xReturn;
    }
#endif /* configUSE_RECURSIVE_MUTEXES */

这个函数是带条件编译的,只有将宏configUSE_RECURSIVE_MUTEXES定义为1才会编译这个函数。

互斥量和递归互斥量的最大区别在于一个递归互斥量可以被已经获取这个递归互斥量的任务重复获取,这个递归调用功能是通过队列结构体成员u.uxRecursiveCallCount实现的。

这个变量用于存储递归调用的次数,每次获取递归互斥量后,这个变量加1,在释放递归互斥量后,这个变量减1。

只有这个变量减到0,即释放和获取的次数相等时,互斥量才能再次有效,使用入队函数释放一个递归互斥量。

5.获取递归互斥量

函数xSemaphoreTakeRecursive()用于获取一个递归互斥量。像其它信号量一样,xSemaphoreTakeRecursive()也是一个宏定义,它最终使用现有的队列机制,实际执行的函数是xQueueTakeMutexRecursive(),这个宏定义如下所示:

#definexSemaphoreTakeRecursive( xMutex, xBlockTime )       \
               xQueueTakeMutexRecursive( ( xMutex ), ( xBlockTime ) )

获取递归互斥量具有阻塞超时参数,如果互斥量正被别的任务使用,可以阻塞设定的时间。

我们重点来看函数xQueueTakeMutexRecursive()的实现过程。经过整理后(去除跟踪调试语句)的源码如下所示:

#if ( configUSE_RECURSIVE_MUTEXES == 1 )
    BaseType_txQueueTakeMutexRecursive( QueueHandle_t xMutex, TickType_txTicksToWait )
    {
    BaseType_t xReturn;
    Queue_t * const pxMutex = ( Queue_t * ) xMutex;
    /*互斥量和递归互斥量要在同一个任务中获取和释放,递归互斥量可以在一个任务中多次获取,当第一次获取递归互斥量时,队列结构体成员指针pxMutexHolder指向获取递归互斥量的任务TCB,在此获取这个递归互斥量时,如果这个指针指向的TCB和当前任务TCB相同,只需要将递归次数计数器u.uxRecursiveCallCount加1即可,不需要再操作队列.*/
        if( pxMutex->pxMutexHolder == ( void * )xTaskGetCurrentTaskHandle() )
        {
            ( pxMutex->u.uxRecursiveCallCount)++;
            xReturn = pdPASS;
        }
        else
        {
            /*调用出队函数*/
            xReturn =xQueueGenericReceive( pxMutex, NULL, xTicksToWait, pdFALSE );
 
            /* 成功获取递归互斥量后,要将递归次数计数器加1*/
            if( xReturn != pdFAIL )
            {
                ( pxMutex->u.uxRecursiveCallCount)++;
            }
        }
 
        return xReturn;
    }
#endif /* configUSE_RECURSIVE_MUTEXES */

这个函数是带条件编译的,只有将宏configUSE_RECURSIVE_MUTEXES定义为1才会编译这个函数。

程序逻辑比较简单,如果是第一次获取这个递归互斥量,直接使用出队函数,成功后将递归次数计数器加1;

如果是第二次或者更多次获取这个递归互斥量,则只需要将递归次数计数器加1即可。

‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧  END  ‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧

推荐阅读:


嵌入式编程专辑
Linux 学习专辑
C/C++编程专辑
Qt进阶学习专辑

关注我的微信公众号,回复“加群”按规则加入技术交流群。


点击“阅读原文”查看更多分享。

李肖遥 公众号“技术让梦想更伟大”,作者:李肖遥,专注嵌入式,只推荐适合你的博文,干货,技术心得,与君共勉。
评论
  •     为控制片内设备并且查询其工作状态,MCU内部总是有一组特殊功能寄存器(SFR,Special Function Register)。    使用Eclipse环境调试MCU程序时,可以利用 Peripheral Registers Viewer来查看SFR。这个小工具是怎样知道某个型号的MCU有怎样的寄存器定义呢?它使用一种描述性的文本文件——SVD文件。这个文件存储在下面红色字体的路径下。    例:南京沁恒  &n
    电子知识打边炉 2025-01-04 20:04 12浏览
  • 在快速发展的能源领域,发电厂是发电的支柱,效率和安全性至关重要。在这种背景下,国产数字隔离器已成为现代化和优化发电厂运营的重要组成部分。本文探讨了这些设备在提高性能方面的重要性,同时展示了中国在生产可靠且具有成本效益的数字隔离器方面的进步。什么是数字隔离器?数字隔离器充当屏障,在电气上将系统的不同部分隔离开来,同时允许无缝数据传输。在发电厂中,它们保护敏感的控制电路免受高压尖峰的影响,确保准确的信号处理,并在恶劣条件下保持系统完整性。中国国产数字隔离器经历了重大创新,在许多方面达到甚至超过了全球
    克里雅半导体科技 2025-01-03 16:10 121浏览
  • 光耦合器,也称为光隔离器,是一种利用光在两个隔离电路之间传输电信号的组件。在医疗领域,确保患者安全和设备可靠性至关重要。在众多有助于医疗设备安全性和效率的组件中,光耦合器起着至关重要的作用。这些紧凑型设备经常被忽视,但对于隔离高压和防止敏感医疗设备中的电气危害却是必不可少的。本文深入探讨了光耦合器的功能、其在医疗应用中的重要性以及其实际使用示例。什么是光耦合器?它通常由以下部分组成:LED(发光二极管):将电信号转换为光。光电探测器(例如光电晶体管):检测光并将其转换回电信号。这种布置确保输入和
    腾恩科技-彭工 2025-01-03 16:27 157浏览
  • 车身域是指负责管理和控制汽车车身相关功能的一个功能域,在汽车域控系统中起着至关重要的作用。它涵盖了车门、车窗、车灯、雨刮器等各种与车身相关的功能模块。与汽车电子电气架构升级相一致,车身域发展亦可以划分为三个阶段,功能集成愈加丰富:第一阶段为分布式架构:对应BCM车身控制模块,包含灯光、雨刮、门窗等传统车身控制功能。第二阶段为域集中架构:对应BDC/CEM域控制器,在BCM基础上集成网关、PEPS等。第三阶段为SOA理念下的中央集中架构:VIU/ZCU区域控制器,在BDC/CEM基础上集成VCU、
    北汇信息 2025-01-03 16:01 173浏览
  • PLC组态方式主要有三种,每种都有其独特的特点和适用场景。下面来简单说说: 1. 硬件组态   定义:硬件组态指的是选择适合的PLC型号、I/O模块、通信模块等硬件组件,并按照实际需求进行连接和配置。    灵活性:这种方式允许用户根据项目需求自由搭配硬件组件,具有较高的灵活性。    成本:可能需要额外的硬件购买成本,适用于对系统性能和扩展性有较高要求的场合。 2. 软件组态   定义:软件组态主要是通过PLC
    丙丁先生 2025-01-06 09:23 11浏览
  • 根据Global Info Research项目团队最新调研,预计2030年全球封闭式电机产值达到1425百万美元,2024-2030年期间年复合增长率CAGR为3.4%。 封闭式电机是一种电动机,其外壳设计为密闭结构,通常用于要求较高的防护等级的应用场合。封闭式电机可以有效防止外部灰尘、水分和其他污染物进入内部,从而保护电机的内部组件,延长其使用寿命。 环洋市场咨询机构出版的调研分析报告【全球封闭式电机行业总体规模、主要厂商及IPO上市调研报告,2025-2031】研究全球封闭式电机总体规
    GIRtina 2025-01-06 11:10 16浏览
  • 本文介绍Linux系统更换开机logo方法教程,通用RK3566、RK3568、RK3588、RK3576等开发板,触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。制作图片开机logo图片制作注意事项(1)图片必须为bmp格式;(2)图片大小不能大于4MB;(3)BMP位深最大是32,建议设置为8;(4)图片名称为logo.bmp和logo_kernel.bmp;开机
    Industio_触觉智能 2025-01-06 10:43 15浏览
  • 随着市场需求不断的变化,各行各业对CPU的要求越来越高,特别是近几年流行的 AIOT,为了有更好的用户体验,CPU的算力就要求更高了。今天为大家推荐由米尔基于瑞芯微RK3576处理器推出的MYC-LR3576核心板及开发板。关于RK3576处理器国产CPU,是这些年的骄傲,华为手机全国产化,国人一片呼声,再也不用卡脖子了。RK3576处理器,就是一款由国产是厂商瑞芯微,今年第二季推出的全新通用型的高性能SOC芯片,这款CPU到底有多么的高性能,下面看看它的几个特性:8核心6 TOPS超强算力双千
    米尔电子嵌入式 2025-01-03 17:04 2浏览
  • 在测试XTS时会遇到修改产品属性、SElinux权限、等一些内容,修改源码再编译很费时。今天为大家介绍一个便捷的方法,让OpenHarmony通过挂载镜像来修改镜像内容!触觉智能Purple Pi OH鸿蒙开发板演示。搭载了瑞芯微RK3566四核处理器,树莓派卡片电脑设计,支持开源鸿蒙OpenHarmony3.2-5.0系统,适合鸿蒙开发入门学习。挂载镜像首先,将要修改内容的镜像传入虚拟机当中,并创建一个要挂载镜像的文件夹,如下图:之后通过挂载命令将system.img镜像挂载到sys
    Industio_触觉智能 2025-01-03 11:39 113浏览
  • 物联网(IoT)的快速发展彻底改变了从智能家居到工业自动化等各个行业。由于物联网系统需要高效、可靠且紧凑的组件来处理众多传感器、执行器和通信设备,国产固态继电器(SSR)已成为满足中国这些需求的关键解决方案。本文探讨了国产SSR如何满足物联网应用的需求,重点介绍了它们的优势、技术能力以及在现实场景中的应用。了解物联网中的固态继电器固态继电器是一种电子开关设备,它使用半导体而不是机械触点来控制负载。与传统的机械继电器不同,固态继电器具有以下优势:快速切换:确保精确快速的响应,这对于实时物联网系统至
    克里雅半导体科技 2025-01-03 16:11 164浏览
  • 自动化已成为现代制造业的基石,而驱动隔离器作为关键组件,在提升效率、精度和可靠性方面起到了不可或缺的作用。随着工业技术不断革新,驱动隔离器正助力自动化生产设备适应新兴趋势,并推动行业未来的发展。本文将探讨自动化的核心趋势及驱动隔离器在其中的重要角色。自动化领域的新兴趋势智能工厂的崛起智能工厂已成为自动化生产的新标杆。通过结合物联网(IoT)、人工智能(AI)和机器学习(ML),智能工厂实现了实时监控和动态决策。驱动隔离器在其中至关重要,它确保了传感器、执行器和控制单元之间的信号完整性,同时提供高
    腾恩科技-彭工 2025-01-03 16:28 161浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦