程序员人生 网站导航

FreeRTOS高级篇6---FreeRTOS信号量分析

栏目:综合技术时间:2016-06-25 15:35:06

         FreeRTOS的信号量包括2进制信号量、计数信号量、互斥信号量(以后简称互斥量)和递归互斥信号量(以后简称递归互斥量)。关于它们的区分可以参考《 FreeRTOS系列第19篇---FreeRTOS信号量》1文。

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

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

1.信号量创建

         在《FreeRTOS高级篇5---FreeRTOS队列分析》中,我们分析了队列的实现进程,包括队列创建、入队和出队操作。在那篇文章中我们说过,创建队列API函数实际是调用通用队列创建函数xQueueGenericCreate()来实现的。其实,不但创建队列实际调用通用队列创建函数,2进制信号量、计数信号量、互斥量和递归互斥量也都直接或间接使用这个函数,如表1⑴所示。表1⑴中红色字体表示是间接调用xQueueGenericCreate()函数。

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

 

1.1.创建2进制信号量

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

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

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

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


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

        也许不止1人像我1样奇怪,创建1个没有队列项存储空间的队列,信号量用甚么表示?其实2进制信号量的释放和获得都是通过操作队列结构体成员uxMessageWaiting来实现的(图1⑴红色部份,uxMessageWaiting表示队列中当前队列项的个数)。经过初始化后,变量uxMessageWaiting为0,这说明队列为空,也就是信号量处于无效状态。在使用API函数xSemaphoreTake()获得信号之前,需要先释放1个信号量。后面讲到2进制信号量释放和获得时还会详细介绍。

1.2.创建计数信号量

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

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

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

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

         我们来看1下函数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()来创建1个队列,队列项的数目由参数uxMaxCount指定,每一个队列项的大小由宏queueSEMAPHORE_QUEUE_ITEM_LENGTH指出,我们找到这个宏定义发现,这个宏被定义为0,也就是说创建的队列只有队列数据结构存储空间而没有队列项存储空间。

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


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

1.3创建互斥量

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

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

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

         我们来看1下函数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()函数会依照通用队列的方式设置所有队列结构体成员,但是我们是要创建互斥量.因此需要对1些结构体成员重新赋值. */ pxNewQueue->pxMutexHolder = NULL; pxNewQueue->uxQueueType =queueQUEUE_IS_MUTEX; //NULL /* 用于递归互斥量创建 */ pxNewQueue->u.uxRecursiveCallCount = 0; /* 使用1个预期状态启动信号量 */ ( void ) xQueueGenericSend( pxNewQueue, NULL, ( TickType_t ) 0U, queueSEND_TO_BACK); } return pxNewQueue; } #endif /* configUSE_MUTEXES */

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

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

         如果队列创建成功,通用队列创建函数还会依照通用队列的方式 初始化所有队列结构体成员。但是这里要创建的是互斥量,所以有1些结构体成员必须重新赋值。在这段代码中,可能你会疑惑,队列结构体成员中,并没有pxMutexHolder和uxQueueType!其实这两个标识符只是宏定义,是专门为互斥量而定义的,以下所示:

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

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

         最后调用函数xQueueGenericSend()释放1个互斥量,相当于互斥量创建后是有效的,可以直接使用获得信号量API函数来获得这个互斥量。如果某资源同时只准1个任务访问,可以用互斥量保护这个资源。这个资源1定是存在的,所以创建互斥量时会先释放1个互斥量,表示这个资源可使用。任务想访问资源时,先获得互斥量,等使用完资源后,再释放它。也就是说互斥量1旦创建好后,要先获得,后释放,要在同1个任务中获得和释放。这也是互斥量和2进制信号量的1个重要区分,2进制信号量可以在随意1个任务中获得或释放,然后也能够在任意1个任务中释放或获得。互斥量不同于2进制信号量的还有:互斥量具有优先级继承机制,2进制信号量没有,互斥量不可以用于中断服务程序,2进制信号量可以。

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


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

1.4创建递归互斥量

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

#definexSemaphoreCreateRecursiveMutex() \ xQueueCreateMutex(queueQUEUE_TYPE_RECURSIVE_MUTEX, NULL )

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

         创建互斥量和创建递归互斥量是调用的同1个函数xQueueCreateMutex(),至于参数queueQUEUE_TYPE_RECURSIVE_MUTEX,我们在FreeRTOS1文中已知道,它只是用于可视化调试,因此创建互斥量和创建递归互斥量可以看做是1样的,初始化后的递归互斥量对象内存也和互斥量1样,如图1⑶所示。

2.释放信号量

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

2.1 xSemaphoreGive()

         用于释放1个信号量,不带中断保护。被释放的信号量可以是2进制信号量、计数信号量和互斥量。注意递归互斥量其实不能使用这个API函数释放。其实信号量释放是1个宏,真正调用的函数是xQueueGenericSend(),宏定义以下:

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

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

         对2进制信号量和计数信号量,根据上1章的内容可以总结出,释放1个信号量的进程实际上可以简化为两种情况:第1,如果队列未满,队列结构体成员uxMessageWaiting加1,判断是不是有阻塞的任务,有的话消除阻塞,然后返回成功信息(pdPASS);第2,如果队列满,返回毛病代码(err_QUEUE_FULL),表示队列满。

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

        优先级继承是个甚么进程呢?我们举个例子。某个资源X同时只能有1个任务访问,现在有任务A和任务C都要访问这个资源,任务A的优先级为1,任务C的优先级为10,所以任务C的优先级大于任务A的优先级。我们用互斥量保护资源X,并且当前任务A正在访问资源X。在任务A访问资源X的进程中,来了1个中断,中断事件使得任务C履行。任务C履行的进程中,也想访问资源X,但是由于资源X还被任务A独占着,所以任务C没法获得互斥量,会进入阻塞状态。此时,低优先级任务A会继承高优先级任务C的优先级,任务A的优先级临时的被提升,优先级变成10。这个机制能够将已产生的优先级反转影响下降到最小。

        那末甚么是优先级反转呢?还是看上面的例子,任务C的优先级高于任务A,但是任务C由于没有取得互斥量而进入阻塞,只能等待低优先级的任务A释放互斥量后才能运行,这类情况就是优先级反转。

        那为何优先级继承可以下降优先级反转的影响呢?还是看上面的例子,不过我们再增加1个优先级为5的任务B,这3个任务都处于就绪状态。如果没有优先级继承机制,3个任务的优先级顺序为任务C>任务B>任务A。当任务C由于得不到互斥量而阻塞后,任务B会获得CPU权限,等到任务B主动或被动让出CPU后,任务A才会履行,任务A释放互斥量后,任务C才能得到运行。再看1下有优先级继承的情况,当任务C由于得不到互斥量而阻塞后,任务A继承任务C的优先级,现在3个任务的优先级顺序为任务C=任务A>任务B。当任务C由于得不到互斥量而阻塞后,任务A会取得CPU权限,等到任务A释放互斥量后,任务C就会得到运行。看,任务C等待的时间变短了。

        有了上面的基础理论,我们就很好理解为何释放互斥量会比较复杂了。还是可以简化为两种情况:第1,如果队列未满,除队列结构体成员uxMessageWaiting加1外,还要判断获得互斥量的任务是不是有优先级继承,如果有的话,还要将任务的优先级恢复到原始值。固然,恢复到原来值也是有条件的,就是该任务必须在没有使用其它互斥量的情况下,才能将继承的优先级恢复到原始值。然后判断是不是有阻塞的任务,有的话消除阻塞,最后返回成功信息(pdPASS);第2,如果如果队列满,返回毛病代码(err_QUEUE_FULL),表示队列满。

2.2xSemaphoreGiveFromISR()

         用于释放1个信号量,带中断保护。被释放的信号量可以是2进制信号量和计数信号量。和普通版本的释放信号量API函数不同,它不能释放互斥量,这是由于互斥量不可以在中断中使用!互斥量的优先级继承机制只能在任务中起作用,在中断中毫无意义。带中断保护的信号量释放其实也是1个宏,真正调用的函数是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 ) { /* 1个任务可以获得多个互斥量,但是只能有1个继承优先级,如果任务是互斥量的持有者,则互斥量不允许在中断服务程序中释放.因此这里不需要判断是不是要恢复任务的原始优先级值,只是简单更新队列项计数器. */ ++( 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.获得信号量

         不管2进制信号量、计数信号量还是互斥量,它们都使用相同的获得和释放API函数。释获得信号量会消耗信号量,如果获得信号量失败,任务可能会阻塞,阻塞时间由函数参数xBlockTime指定,如果为0,则直接返回,不阻塞。获得信号量分为不带中断保护和带中断保护两个版本。

3.1 xSemaphoreTake

         用于获得信号量,不带中断保护。获得的信号量可以是2进制信号量、计数信号量和互斥量。注意递归互斥量其实不能使用这个API函数获得。其实获得信号量是1个宏,真正调用的函数是xQueueGenericReceive (),宏定义以下:

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

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

         对2进制信号量和计数信号量,可以简化为3种情况:第1,如果队列不为空,队列结构体成员uxMessageWaiting减1,判断是不是有因入队而阻塞的任务,有的话消除阻塞,然后返回成功信息(pdPASS);第2,如果队列为空并且阻塞时间为0,则直接返回毛病码(errQUEUE_EMPTY),表示队列为空;第3,如果队列为空并且阻塞时间不为0,则任务会由于等待信号量而进入阻塞状态,任务会被挂接到延时列表中。

        对互斥量,也能够简化为3种情况,但是进程要复杂1些:第1,如果队列不为空,队列结构体成员uxMessageWaiting减1、将当前任务TCB结构体成员uxMutexesHeld加1,表示任务获得互斥量的个数、将队列结构体成员指针pxMutexHolder指向任务TCB、判断是不是有因入队而阻塞的任务,有的话消除阻塞,然后返回成功信息(pdPASS);第2,如果队列为空并且阻塞时间为0,则直接返回毛病码(errQUEUE_EMPTY),表示队列为空;第3,如果队列为空并且阻塞时间不为0,则任务会由于等待信号量而进入阻塞状态,在将任务挂接到延时列表之前,会判断当前任务和具有互斥量的任务优先级哪一个高,如果当前任务优先级高,则具有互斥量的任务继承担前任务优先级。

3.2xSemaphoreTakeFromISR()

         用于获得信号量,带中断保护。获得的信号量可以是2进制信号量和计数信号量。和普通版本的获得信号量API函数不同,它不能获得互斥量,这是由于互斥量不可以在中断中使用!互斥量的优先级继承机制只能在任务中起作用,在中断中毫无意义。带中断保护的获得信号量其实也是1个宏,真正调用的函数是xQueueReceiveFromISR (),宏定义以下:

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

        一样由于不触及互斥量,不触及阻塞,函数xQueueReceiveFromISR ()一样异常简单:如果队列为空,直接返回毛病代码(pdFAIL);如果队列非空,则将队列结构体成员uxMessageWaiting减1,然后视队列是不是上锁而决定是不是消除任务阻塞(如果有得话)。

4.释放递归互斥量

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

         像其它信号量1样,xSemaphoreGiveRecursive()也是1个宏定义,它终究使用现有的队列机制,实际履行的函数是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; /* 互斥量和递归互斥量要在同1个任务中获得和释放,当获得互斥量或递归互斥量时,队列结构体成员指针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 ) { /* 调用入队函数释放1个互斥量,注意阻塞时间(由宏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才会编译这个函数。

         互斥量和递归互斥量的最大区分在于1个递归互斥量可以被已获得这个递归互斥量的任务重复获得,这个递归调用功能是通过队列结构体成员u.uxRecursiveCallCount实现的。这个变量用于存储递归调用的次数,每次获得递归互斥量后,这个变量加1,在释放递归互斥量后,这个变量减1。只有这个变量减到0,即释放和获得的次数相等时,互斥量才能再次有效,使用入队函数释放1个递归互斥量。

5.获得递归互斥量

         函数xSemaphoreTakeRecursive()用于获得1个递归互斥量。像其它信号量1样,xSemaphoreTakeRecursive()也是1个宏定义,它终究使用现有的队列机制,实际履行的函数是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; /*互斥量和递归互斥量要在同1个任务中获得和释放,递归互斥量可以在1个任务中屡次获得,当第1次获得递归互斥量时,队列结构体成员指针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;如果是第2次或更屡次获得这个递归互斥量,则只需要将递归次数计数器加1便可。


------分隔线----------------------------
------分隔线----------------------------

最新技术推荐