19.5 任务控制块

同样都是使用队列的结构体,只不过表示的意义不同。

  1. typedef struct QueueDefinition
  2. {
  3. int8_t *pcHead; // pcHead 指向队列消息存储区起始位置,即第一个消息空间。
  4. int8_t *pcTail; // pcTail 指向队列消息存储区结束位置地址。
  5. int8_t *pcWriteTo; // pcWriteTo 指向队列消息存储区下一个可用消息空间。
  6. union
  7. {
  8. int8_t *pcReadFrom;
  9. UBaseType_t uxRecursiveCallCount; // 1
  10. } u;
  11. List_t xTasksWaitingToSend;
  12. List_t xTasksWaitingToReceive;
  13. volatile UBaseType_t uxMessagesWaiting; // 2 1 存在的消息个数
  14. UBaseType_t uxLength; // 3 1
  15. UBaseType_t uxItemSize; // 4 0
  16. volatile int8_t cRxLock;
  17. volatile int8_t cTxLock;
  18. #if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
  19. uint8_t ucStaticallyAllocated;
  20. #endif
  21. #if ( configUSE_QUEUE_SETS == 1 )
  22. struct QueueDefinition *pxQueueSetContainer;
  23. #endif
  24. #if ( configUSE_TRACE_FACILITY == 1 )
  25. UBaseType_t uxQueueNumber;
  26. uint8_t ucQueueType;
  27. #endif
  28. } xQUEUE;
  29. typedef xQUEUE Queue_t;
  1. pcReadFrom 与 uxRecursiveCallCount 是一对互斥变量, 使用联合体用来确保两个互斥的结构体成员不会同时出现。 当结构体用于队列时, pcReadFrom 指向出队消息空间的最后一个,见文知义,就是读取消息时候是从 pcReadFrom 指向的空间读取消息内容。 当结构体用于互斥量时, uxRecursiveCallCount 用于计数,记录递归互斥量被“调用” 的次数。
  2. 如果控制块结构体是用于消息队列: uxMessagesWaiting 用来记录当前消息队列的消息个数; 如果控制块结构体被用于互斥量的时候, 这个值就表示有效互斥量个数,这个值是 1 则表示互斥量有效,如果是 0 则表示互斥量无效.
  3. 如果控制块结构体是用于消息队列: uxLength 表示队列的长度,也就是能存放多少消息; 如果控制块结构体被用于互斥量的时候, uxLength 表示最大的信号量可用个数, uxLength 最大为 1
  4. 如果控制块结构体是用于消息队列: uxItemSize 表示单个消息的大小; 如果控制块结构体被用于互斥量的时候, 则无需存储空间,为 0 即可。

19.6 互斥量函数接口详解

19.6.1 互斥量创建函数xSemaphoreCreateMutex()

xSemaphoreCreateMutex()用于创建一个互斥量, 并返回一个互斥量句柄。 该句柄的原型是一个 void 型的指针,在使用之前必须先由用户定义一个互斥量句柄。要想使用该函数必须在 FreeRTOSConfig.h 中把宏 configSUPPORT_DYNAMIC_ALLOCATION 定义为 1,即开启动态内存分配, 其实该宏在 FreeRTOS.h 中默认定义为 1, 即所有 FreeRTOS 的对象在创建的时候都默认使用动态内存分配方案, 同时还需在 FreeRTOSConfig.h 中把configUSE_MUTEXES 宏定义打开, 表示使用互斥量。
函数原型:xSemaphoreCreateMutex()

  1. #if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
  2. #define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )
  3. #endif

创建互斥量其实是调用xQueueCreateMutex函数:

  1. #if( ( configUSE_MUTEXES == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
  2. QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType )
  3. {
  4. Queue_t *pxNewQueue;
  5. const UBaseType_t uxMutexLength = ( UBaseType_t ) 1,
  6. uxMutexSize = ( UBaseType_t ) 0;
  7. pxNewQueue = ( Queue_t * ) xQueueGenericCreate(uxMutexLength,
  8. uxMutexSize,
  9. ucQueueType ); // 1
  10. prvInitialiseMutex( pxNewQueue ); // 2
  11. return pxNewQueue;
  12. }
  13. #endif /* configUSE_MUTEXES */

只有将宏 configUSE_MUTEXES 定义为 1 才会编译这个函数。

  1. 其实互斥量的创建也是调用 xQueueGenericCreate()函数进行创建。

uxQueueLength 为 1 表示创建的队列长度为 1。
uxMutexSize 的值为 0,表示创建的消息空间(队列项)大小为 0,因为这个所谓的“消息队列”其实并不是用于存储消息的,而是被用作互斥量
ucQueueType 表示的是创建队列的类型

  1. #define queueQUEUE_TYPE_BASE ( ( uint8_t ) 0U )
  2. #define queueQUEUE_TYPE_SET ( ( uint8_t ) 0U )
  3. #define queueQUEUE_TYPE_MUTEX ( ( uint8_t ) 1U )
  4. #define queueQUEUE_TYPE_COUNTING_SEMAPHORE ( ( uint8_t ) 2U )
  5. #define queueQUEUE_TYPE_BINARY_SEMAPHORE ( ( uint8_t ) 3U )
  6. #define queueQUEUE_TYPE_RECURSIVE_MUTEX ( ( uint8_t ) 4U )

调用函数prvInitialiseMutex() 初始化互斥信号量:

  1. #define pxMutexHolder pcTail
  2. #define uxQueueType pcHead
  3. #define queueQUEUE_IS_MUTEX NULL
  4. static void prvInitialiseMutex( Queue_t *pxNewQueue )
  5. {
  6. if( pxNewQueue != NULL )
  7. {
  8. pxNewQueue->pxMutexHolder = NULL;
  9. pxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX;
  10. /* In case this is a recursive mutex. */
  11. pxNewQueue->u.uxRecursiveCallCount = 0; // 2
  12. traceCREATE_MUTEX( pxNewQueue );
  13. /* Start with the semaphore in the expected state. */
  14. ( void ) xQueueGenericSend( pxNewQueue, NULL, ( TickType_t ) 0U, queueSEND_TO_BACK );
  15. }
  16. else
  17. {
  18. traceCREATE_MUTEX_FAILED();
  19. }
  20. }
  1. FreeRTOS 用宏定义的方式来重新定义了结构体中的 pcTail 与 pcHead 成员变量,更方便阅读。为什么要这样子呢?我们知道, pcTail 与 pcHead 用于指向消息存储区域的, 但是如果队列用作互斥量, 那么我们就无需理会消息存储区域了, 因为都没有消息存储区域,但是互斥量有个很重要的特性,那就是优先级继承机制,所以,我们要知道持有互斥量的任务是哪一个,因为只有持有互斥量的任务才能得到互斥量的所有权,所以, pxMutexHolder就被用于指向持有互斥量的任务控制块, 现在初始化的时候, 就初始化为 NULL,表示没有任务持有互斥量。 uxQueueType 表示队列的类型, 设置为 queueQUEUE_IS_MUTEX(NULL),表示的是用作互斥量。
  2. 如果是递归互斥量 的 话 , 还需要联合体成员 量u.uxRecursiveCallCount 初始化一下
  3. 调用 xQueueGenericSend()函数释放互斥量,在创建成功的时候互斥量默认是有效的。说明现在又1个互斥量是可用的。

image.png
使用实例:

  1. SemaphoreHandle_t MuxSem_Handle;
  2. void vATask( void * pvParameters )
  3. {
  4. /* 创建一个互斥量 */
  5. MuxSem_Handle= xSemaphoreCreateMutex();
  6. if (MuxSem_Handle!= NULL ) {
  7. /* 互斥量创建成功 */
  8. }
  9. }

19.6.2 递归互斥量创建函数xSemaphoreCreateRecursiveMutex()

xSemaphoreCreateRecursiveMutex()用于创建一个递归互斥量, 不是递归的互斥量由函数 xSemaphoreCreateMutex() 或 xSemaphoreCreateMutexStatic()创建。
递归互斥量,它可以被同一个任务获取很多次, 获取多少次就需要释放多少次。递归信号量与互斥量一样,都实现了优先级继承机制,可以降低优先级反转的危害。
configUSE_RECURSIVE_MUTEXES
实际调用函数xQueueCreateMutex() 。
image.png
其实 xSemaphoreCreateRecursiveMutex()实际调用的函数就是 xQueueCreateMutex()函数。
使用实例:

  1. SemaphoreHandle_t xMutex;
  2. void vATask( void * pvParameters )
  3. {
  4. /* 创建一个递归互斥量 */
  5. xMutex = xSemaphoreCreateRecursiveMutex();
  6. if ( xMutex != NULL ) {
  7. /* 递归互斥量创建成功 */
  8. }
  9. }

19.6.3 互斥量删除函数vSemaphoreDelete()

直接调用 vSemaphoreDelete()函数进行删除即可

19.6.4 互斥信号量获取函数xSemaphoreTake()

Receive

这个函数就有互斥量优先级继承相关的实现。
当互斥量处于开锁的状态, 任务才能获取互斥量成功,当任务持有了某个互斥量的时候,其它任务就无法获取这个互斥量,需要等到持有互斥量的任务进行释放后,其他任务才能获取成功, 任务通过互斥量获取函数来获取互斥量的所有权。
任务对互斥量的所有权是独占的,任意时刻互斥量只能被一个任务持有,如果互斥量处于开锁状态,那么获取该互斥量的任务将成功获得该互斥量,并拥有互斥量的使用权。
如果互斥量处于闭锁状态,获取该互斥量的任务将无法获得互斥量, 任务将被挂起,在任务被挂起之前,会进行优先级继承,如果当前任务优先级比持有互斥量的任务优先级高,那么将会临时提升持有互斥量任务的优先级。

  1. #define xSemaphoreTake( xSemaphore, xBlockTime ) \
  2. xQueueGenericReceive( ( QueueHandle_t ) ( xSemaphore ), \
  3. NULL, \
  4. (xBlockTime ), \
  5. pdFALSE )

其实就是消息队列获取函数,只不过如果是使用了互斥量的时候,这个函数会稍微有点不一样,因为互斥量本身的优先级继承机制,所以,在这个函数里面会使用宏定义进行编译,如果获取的对象是互斥量,那么这个函数就拥有优先级继承算法,如果获取对象不是互斥量,就没有优先级继承机制。

  1. BaseType_t xQueueGenericReceive( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait, const BaseType_t xJustPeeking )
  2. {
  3. BaseType_t xEntryTimeSet = pdFALSE;
  4. TimeOut_t xTimeOut;
  5. int8_t *pcOriginalReadPosition;
  6. Queue_t * const pxQueue = ( Queue_t * ) xQueue;
  7. for( ;; )
  8. {
  9. taskENTER_CRITICAL();
  10. {
  11. const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;
  12. /* 看看队列中有没有消息 */
  13. if( uxMessagesWaiting > ( UBaseType_t ) 0 )
  14. {
  15. /*防止仅仅是读取消息,而不进行消息出队操作*/
  16. pcOriginalReadPosition = pxQueue->u.pcReadFrom;
  17. /* 拷贝消息到用户指定存放区域 pvBuffer */
  18. prvCopyDataFromQueue( pxQueue, pvBuffer );
  19. if( xJustPeeking == pdFALSE )
  20. {
  21. traceQUEUE_RECEIVE( pxQueue );
  22. /* 获取了消息,当前消息队列的消息个数需要减一 */
  23. pxQueue->uxMessagesWaiting = uxMessagesWaiting - 1;
  24. /* 如果系统支持使用互斥量 */
  25. #if ( configUSE_MUTEXES == 1 )
  26. {
  27. /* 如果队列类型是互斥量 */
  28. if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
  29. {
  30. /* 获取当前任务控制块 */
  31. pxQueue->pxMutexHolder = ( int8_t * ) pvTaskIncrementMutexHeldCount();
  32. }
  33. else
  34. {
  35. mtCOVERAGE_TEST_MARKER();
  36. }
  37. }
  38. #endif /* configUSE_MUTEXES */
  39. /* 判断一下消息队列中是否有等待发送消息的任务 */
  40. if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
  41. {
  42. /* 将任务从阻塞中恢复 */
  43. if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
  44. {
  45. /* 如果被恢复的任务优先级比当前任务高,会进行一次任务切换 */
  46. queueYIELD_IF_USING_PREEMPTION();
  47. }
  48. else
  49. {
  50. mtCOVERAGE_TEST_MARKER();
  51. }
  52. }
  53. else
  54. {
  55. mtCOVERAGE_TEST_MARKER();
  56. }
  57. }
  58. else /* 这个是不释放消息 */
  59. {
  60. traceQUEUE_PEEK( pxQueue );
  61. pxQueue->u.pcReadFrom = pcOriginalReadPosition;
  62. if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
  63. {
  64. if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
  65. {
  66. /* The task waiting has a higher priority than this task. */
  67. queueYIELD_IF_USING_PREEMPTION();
  68. }
  69. else
  70. {
  71. mtCOVERAGE_TEST_MARKER();
  72. }
  73. }
  74. else
  75. {
  76. mtCOVERAGE_TEST_MARKER();
  77. }
  78. }
  79. taskEXIT_CRITICAL();
  80. return pdPASS;
  81. }
  82. else /* 消息队列中没有消息可读 */
  83. {
  84. if( xTicksToWait == ( TickType_t ) 0 )
  85. {
  86. /* The queue was empty and no block time is specified (or
  87. the block time has expired) so leave now. */
  88. taskEXIT_CRITICAL();
  89. traceQUEUE_RECEIVE_FAILED( pxQueue );
  90. return errQUEUE_EMPTY;
  91. }
  92. else if( xEntryTimeSet == pdFALSE )
  93. {
  94. /* 初始化阻塞超时结构体变量,初始化进入
  95. 阻塞的时间 xTickCount 和溢出次数 xNumOfOverflows */
  96. vTaskSetTimeOutState( &xTimeOut );
  97. xEntryTimeSet = pdTRUE;
  98. }
  99. else
  100. {
  101. mtCOVERAGE_TEST_MARKER();
  102. }
  103. }
  104. }
  105. taskEXIT_CRITICAL();
  106. vTaskSuspendAll();
  107. prvLockQueue( pxQueue );
  108. /* 检查超时时间是否已经过去了*/
  109. if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
  110. {
  111. /* 如果队列还是空的 */
  112. if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
  113. {
  114. traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue );
  115. /* 如果系统支持使用互斥量 */
  116. #if ( configUSE_MUTEXES == 1 )
  117. {
  118. if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
  119. {
  120. taskENTER_CRITICAL();
  121. {
  122. /* 进行优先级继承 */
  123. vTaskPriorityInherit( ( void * ) pxQueue->pxMutexHolder );
  124. }
  125. taskEXIT_CRITICAL();
  126. }
  127. else
  128. {
  129. mtCOVERAGE_TEST_MARKER();
  130. }
  131. }
  132. #endif
  133. /* 将当前任务添加到队列的等待接收列表中
  134. 以及阻塞延时列表,阻塞时间为用户指定的超时时间 xTicksToWait */
  135. vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );
  136. prvUnlockQueue( pxQueue );
  137. if( xTaskResumeAll() == pdFALSE )
  138. {
  139. /* 如果有任务优先级比当前任务高,会进行一次任务切换 */
  140. portYIELD_WITHIN_API();
  141. }
  142. else
  143. {
  144. mtCOVERAGE_TEST_MARKER();
  145. }
  146. }
  147. else
  148. {
  149. /* 如果队列有消息了,就再试一次获取消息 */
  150. prvUnlockQueue( pxQueue );
  151. ( void ) xTaskResumeAll();
  152. }
  153. }
  154. else
  155. {
  156. /* 超时时间已过,退出 */
  157. prvUnlockQueue( pxQueue );
  158. ( void ) xTaskResumeAll();
  159. /* 如果队列还是空的,返回错误代码 errQUEUE_EMPTY */
  160. if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
  161. {
  162. traceQUEUE_RECEIVE_FAILED( pxQueue );
  163. return errQUEUE_EMPTY;
  164. }
  165. else
  166. {
  167. mtCOVERAGE_TEST_MARKER();
  168. }
  169. }
  170. }
  171. }
  172. /*-----------------------------------------------------------*/
  1. 如果互斥量有效,调用获取互斥量函数后结构体成员变量 uxMessageWaiting 会减 1,然后将队列结构体成员指针 pxMutexHolder 指向任务控制块, 表示这个互斥量被哪个任务持有, 只有这个任务才拥有互斥量的所 有权 ,并且该任务的控制块结构体成员uxMutexesHeld 会加 1,表示任务已经获取到互斥量。

    1. void *pvTaskIncrementMutexHeldCount( void )
    2. {
    3. /* If xSemaphoreCreateMutex() is called before any tasks have been created
    4. then pxCurrentTCB will be NULL. */
    5. if( pxCurrentTCB != NULL )
    6. {
    7. // + 1
    8. ( pxCurrentTCB->uxMutexesHeld )++;
    9. }
    10. // 返回TCB
    11. return pxCurrentTCB;
    12. }
  2. 如果此时互斥量是无效状态并且用户指定的阻塞时间为 0,则直接返回错误码(errQUEUE_EMPTY)

  3. 而如果用户指定的阻塞超时时间不为 0,则当前任务会因为等待互斥量有效而进入阻塞状态,在将任务添加到延时列表之前,会判断当前任务和拥有互斥量的任务优先级哪个更高,如果当前任务优先级高,则拥有互斥量的任务继承当前任务优先级, 也就是我们说的优先级继承机制

vTaskPriorityInherit() 真正的继承机制

  1. void vTaskPriorityInherit( TaskHandle_t const pxMutexHolder )
  2. {
  3. TCB_t * const pxTCB = ( TCB_t * ) pxMutexHolder;
  4. if( pxMutexHolder != NULL )
  5. {
  6. /* 判断当前任务与持有互斥量任务的优先级 */
  7. /* 如果获取锁的任务的优先级小于当前要获取所的任务的优先级 */
  8. if( pxTCB->uxPriority < pxCurrentTCB->uxPriority )
  9. {
  10. /*如果该事件列表项的值未用于其它操作 则设置优先级 */
  11. if( ( listGET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ) )
  12. & taskEVENT_LIST_ITEM_VALUE_IN_USE ) == 0UL )
  13. {
  14. /* 调整互斥锁持有者等待的事件列表项的优先级 */
  15. listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ),
  16. ( TickType_t ) configMAX_PRIORITIES -
  17. ( TickType_t ) pxCurrentTCB->uxPriority ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
  18. }
  19. else
  20. {
  21. mtCOVERAGE_TEST_MARKER();
  22. }
  23. /* 如果被提升优先级的任务处于就绪列表中 */
  24. if( listIS_CONTAINED_WITHIN( &( pxReadyTasksLists[ pxTCB->uxPriority ] ), &( pxTCB->xStateListItem ) ) != pdFALSE )
  25. {
  26. /* 先将任务从就绪列表中移除,待优先级继承完毕就重新插入就绪列表中 */
  27. if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
  28. {
  29. taskRESET_READY_PRIORITY( pxTCB->uxPriority );
  30. }
  31. else
  32. {
  33. mtCOVERAGE_TEST_MARKER();
  34. }
  35. // 优先级继承
  36. pxTCB->uxPriority = pxCurrentTCB->uxPriority;
  37. prvAddTaskToReadyList( pxTCB );
  38. }
  39. else // 在等待列表中
  40. {
  41. /* 暂时提升持有互斥量任务的优先级,提升到与当前任务优先级一致*/
  42. pxTCB->uxPriority = pxCurrentTCB->uxPriority;
  43. }
  44. traceTASK_PRIORITY_INHERIT( pxTCB, pxCurrentTCB->uxPriority );
  45. }
  46. else
  47. {
  48. mtCOVERAGE_TEST_MARKER();
  49. }
  50. }
  51. else
  52. {
  53. mtCOVERAGE_TEST_MARKER();
  54. }
  55. }
  56. #endif /* configUSE_MUTEXES */
  57. /*-----------------------------------------------------------*/

至此,获取互斥量的操作就完成了,如果任务获取互斥量成功,那么在使用完毕需要立即释放,否则很容易造成其他任务无法获取互斥量,因为互斥量的优先级继承机制是只能将优先级危害降低,而不能完全消除。同时还需注意的是,互斥量是不允许在中断中操作的,因为优先级继承机制在中断是无意义的。

使用实例:

  1. static void HighPriority_Task(void* parameter)
  2. {
  3. BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为 pdTRUE */
  4. while(1) {
  5. printf("HighPriority_Task 获取信号量\n");
  6. //获取互斥量 MuxSem,没获取到则一直等待
  7. xReturn = xSemaphoreTake(MuxSem_Handle,/* 互斥量句柄 */
  8. portMAX_DELAY); /* 等待时间 */
  9. if (pdTRUE == xReturn)
  10. printf("HighPriority_Task Runing\n");
  11. LED1_TOGGLE;
  12. printf("HighPriority_Task 释放信号量!\r\n");
  13. xSemaphoreGive( MuxSem_Handle );//释放互斥量
  14. vTaskDelay(1000);
  15. }
  16. }

19.6.5 递归互斥量获取函数xSemaphoreTakeRecursive()

互斥量之前必须由xSemaphoreCreateRecursiveMutex()这个函数创建
image.png

  1. #if ( configUSE_RECURSIVE_MUTEXES == 1 )
  2. BaseType_t xQueueTakeMutexRecursive( QueueHandle_t xMutex, TickType_t xTicksToWait )
  3. {
  4. BaseType_t xReturn;
  5. Queue_t * const pxMutex = ( Queue_t * ) xMutex;
  6. configASSERT( pxMutex );
  7. traceTAKE_MUTEX_RECURSIVE( pxMutex );
  8. /* 如果持有互斥量的任务就是当前任务 */
  9. if( pxMutex->pxMutexHolder == ( void * ) xTaskGetCurrentTaskHandle() ) /*lint !e961 Cast is not redundant as TaskHandle_t is a typedef. */
  10. {
  11. /* u.uxRecursiveCallCount 自加,表示调用了多少次递归互斥量获取 */
  12. ( pxMutex->u.uxRecursiveCallCount )++;
  13. xReturn = pdPASS;
  14. }
  15. else
  16. {
  17. /* 如果持有递归互斥量的任务不是当前任务,就只能等待递归互斥量被释放 */
  18. xReturn = xQueueGenericReceive( pxMutex, NULL, xTicksToWait, pdFALSE );
  19. if( xReturn != pdFAIL )
  20. {
  21. /* 获取递归互斥量成功,记录递归互斥量的获取次数 */
  22. ( pxMutex->u.uxRecursiveCallCount )++;
  23. }
  24. else
  25. {
  26. traceTAKE_MUTEX_RECURSIVE_FAILED( pxMutex );
  27. }
  28. }
  29. return xReturn;
  30. }
  31. #endif /* configUSE_RECURSIVE_MUTEXES */

递归互斥量可以在一个任务中多次获取,当第一次获取递归互斥量时,队列结构体成员指针 pxMutexHolder 指向获取递归互斥量的任务控制块,当任务再次尝试获取这个递归互斥量时,如果任务就是拥有递归互斥量所有权的任务,那么只需要将记录获取递归次数的 成 员 变 量 u.uxRecursiveCallCount 加1即可,不需要再操作队列。

  1. /* 使用互斥量 */
  2. void vAnotherTask( void * pvParameters )
  3. {
  4. /* ... 做其他的事情 */
  5. if ( xMutex != NULL ) {
  6. /* 尝试获取递归信号量。
  7. 如果信号量不可用则等待 10 个 ticks */
  8. if(xSemaphoreTakeRecursive(xMutex,( TickType_t)10)==pdTRUE ) {
  9. /* 获取到递归信号量,可以访问共享资源 */
  10. /* ... 其他功能代码 */
  11. /* 重复获取递归信号量 */
  12. xSemaphoreTakeRecursive( xMutex, ( TickType_t ) 10 );
  13. xSemaphoreTakeRecursive( xMutex, ( TickType_t ) 10 );
  14. /* 释放递归信号量,获取了多少次就要释放多少次 */
  15. xSemaphoreGiveRecursive( xMutex );
  16. xSemaphoreGiveRecursive( xMutex );
  17. xSemaphoreGiveRecursive( xMutex );
  18. /* 现在递归互斥量可以被其他任务获取 */
  19. } else {
  20. /* 没能成功获取互斥量,所以不能安全的访问共享资源 */
  21. }
  22. }
  23. }

递归互斥锁的意义

19.6.6 互斥量释放函数xSemaphoreGive()

send
使用该函数接口时,只有已持有互斥量所有权的任务才能释放它, 当任务调用xSemaphoreGive()函数时会将互斥量变为开锁状态,等待获取该互斥量的任务将被唤醒。如果任务的优先级被互斥量的优先级翻转机制临时提升,那么当互斥量被释放后, 任务的优先级将恢复为原本设定的优先级。

  1. #define xSemaphoreGive( xSemaphore ) \
  2. xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), \
  3. NULL, \
  4. semGIVE_BLOCK_TIME, \
  5. queueSEND_TO_BACK )

互斥量、信号量的释放就是调用 xQueueGenericSend()函数,但是互斥量的处理还是有一些不一样的地方,因为它有优先级继承机制,在释放互斥量的时候我们需要恢复任务的初始优先级,所以,下面我们来看看具体在哪恢复任务的优先级,其实就是prvCopyDataToQueue()这个函数,该函数在 xQueueGenericSend()中被调用。

  1. #if ( configUSE_MUTEXES == 1 )
  2. {
  3. if ( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
  4. {
  5. /* The mutex is no longer being held. */
  6. xReturn = xTaskPriorityDisinherit( ( void * ) pxQueue->pxMutexHolder );
  7. pxQueue->pxMutexHolder = NULL;
  8. } else {
  9. mtCOVERAGE_TEST_MARKER();
  10. }

真正恢复任务的优先级函数其实是调用 xTaskPriorityDisinherit(), 而且系统会将结构体的 pxMutexHolder 成员变量指向 NULL,表示暂时没有任务持有改互斥量, 对结构体成员 uxMessagesWaiting 加 1 操作就代表了释放互 斥量 , 表示此时互斥量是有效的, 其他任务可以来获取。

  1. BaseType_t xTaskPriorityDisinherit( TaskHandle_t const pxMutexHolder )
  2. {
  3. TCB_t * const pxTCB = ( TCB_t * ) pxMutexHolder;
  4. BaseType_t xReturn = pdFALSE;
  5. // 只有当有任务持有互斥量的时候,才会进行释放互斥量的操作。
  6. // 而且必须是持有互斥量的任务才允许释放互斥量,其他任务都没有权利去操作被任务持有
  7. // 的互斥量
  8. if( pxMutexHolder != NULL )
  9. {
  10. configASSERT( pxTCB == pxCurrentTCB );
  11. configASSERT( pxTCB->uxMutexesHeld );
  12. // 在获取的时候加了1,现在要--
  13. ( pxTCB->uxMutexesHeld )--;
  14. // 判断是否发生了优先级的翻转 也就是当前的优先级和uxBasePriority不相等
  15. if( pxTCB->uxPriority != pxTCB->uxBasePriority )
  16. {
  17. /* 如果任务没有持有其他互斥量 */
  18. if( pxTCB->uxMutexesHeld == ( UBaseType_t ) 0 )
  19. {
  20. /* 将任务从就绪列表中删除 */
  21. if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
  22. {
  23. taskRESET_READY_PRIORITY( pxTCB->uxPriority );
  24. }
  25. else
  26. {
  27. mtCOVERAGE_TEST_MARKER();
  28. }
  29. traceTASK_PRIORITY_DISINHERIT( pxTCB, pxTCB->uxBasePriority );
  30. /* 在将任务添加到新的就绪列表之前,恢复任务的初始优先级 */
  31. pxTCB->uxPriority = pxTCB->uxBasePriority;
  32. /* 同时要重置等待事件列表的优先级 */
  33. listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) pxTCB->uxPriority );
  34. /* 将任务重新添加到就绪列表中 */
  35. prvAddTaskToReadyList( pxTCB );
  36. xReturn = pdTRUE;
  37. }
  38. else
  39. {
  40. mtCOVERAGE_TEST_MARKER();
  41. }
  42. }
  43. else
  44. {
  45. mtCOVERAGE_TEST_MARKER();
  46. }
  47. }
  48. else
  49. {
  50. mtCOVERAGE_TEST_MARKER();
  51. }
  52. return xReturn;
  53. }
  54. #endif /* configUSE_MUTEXES */

被释放前的互斥量是处于无效状态, 被释放后互斥量才变得有效,除了结构体成员变量 uxMessageWaiting 加 1 外,还要判断持有互斥量的任务是否有优先级继承,如果有的话,要将任务的优先级恢复到初始值。当然,该任务必须在没有持有其它互斥量的情况下,才能将继承的优先级恢复到原始值。然后判断是否有任务要获取互斥量并且进入阻塞状态,有的话解除阻塞,最后返回成功信息(pdPASS)。

使用实例:

  1. void vATask( void * pvParameters )
  2. {
  3. /* 创建一个互斥量用于保护共享资源 */
  4. xSemaphore = xSemaphoreCreateMutex();
  5. if ( xSemaphore != NULL ) {
  6. // 这个好像不需要
  7. if ( xSemaphoreGive( xSemaphore ) != pdTRUE ) {
  8. /*如果要释放一个互斥量,必须先有第一次的获取*/
  9. }
  10. /* 获取互斥量,不等待 */
  11. if ( xSemaphoreTake( xSemaphore, ( TickType_t ) 0 ) ) {
  12. /* 获取到互斥量,可以访问共享资源 */
  13. /* ... 访问共享资源代码 */
  14. /* 共享资源访问完毕,释放互斥量 */
  15. if ( xSemaphoreGive( xSemaphore ) != pdTRUE ) {
  16. /* 互斥量释放失败,这可不是我们希望的 */
  17. }
  18. }
  19. }
  20. }

19.6.7 递归互斥量释放函数xSemaphoreGiveRecursive()

xSemaphoreGiveRecursive()是一个用于释放递归互斥量的宏

  1. #if( configUSE_RECURSIVE_MUTEXES == 1 )
  2. #define xSemaphoreGiveRecursive( xMutex ) \
  3. xQueueGiveMutexRecursive( ( xMutex ) )
  4. #endif

xSemaphoreGiveRecursive()函数用于释放一个递归互斥量。已经获取递归互斥量的任务可以重复获取该递归互斥量。使用 xSemaphoreTakeRecursive() 函数成功获取几次递归互斥量,就要使用 xSemaphoreGiveRecursive()函数返还几次,在此之前递归互斥量都处于无效状态, 别的任务就无法获取该递归互斥量。 使用该函数接口时,只有已持有互斥量所有权的任务才能释放它,每释放一次该递归互斥量,它的计数值就减 1。当该互斥量的计数值为 0 时(即持有任务已经释放所有的持有操作), 互斥量则变为开锁状态,等待在该互斥量上的任务将被唤醒。 如果任务的优先级被互斥量的优先级翻转机制临时提升,那么当互斥量被释放后, 任务的优先级将恢复为原本设定的优先级。

  1. #if ( configUSE_RECURSIVE_MUTEXES == 1 )
  2. BaseType_t xQueueGiveMutexRecursive( QueueHandle_t xMutex )
  3. {
  4. BaseType_t xReturn;
  5. Queue_t * const pxMutex = ( Queue_t * ) xMutex;
  6. configASSERT( pxMutex );
  7. /* 判断任务是否持有这个递归互斥量 */
  8. if( pxMutex->pxMutexHolder == ( void * ) xTaskGetCurrentTaskHandle() ) /*lint !e961 Not a redundant cast as TaskHandle_t is a typedef. */
  9. {
  10. traceGIVE_MUTEX_RECURSIVE( pxMutex );
  11. /* 调用次数的计数值减一 */
  12. ( pxMutex->u.uxRecursiveCallCount )--;
  13. /* 如果计数值减到 0 */
  14. if( pxMutex->u.uxRecursiveCallCount == ( UBaseType_t ) 0 )
  15. {
  16. /* 释放成功 */
  17. ( void ) xQueueGenericSend( pxMutex, NULL, queueMUTEX_GIVE_BLOCK_TIME, queueSEND_TO_BACK );
  18. }
  19. else
  20. {
  21. mtCOVERAGE_TEST_MARKER();
  22. }
  23. xReturn = pdPASS;
  24. }
  25. else
  26. {
  27. /* 这个任务不具备释放这个互斥量的权利 */
  28. xReturn = pdFAIL;
  29. traceGIVE_MUTEX_RECURSIVE_FAILED( pxMutex );
  30. }
  31. return xReturn;
  32. }
  33. #endif /* configUSE_RECURSIVE_MUTEXES */

使用示例:

  1. {
  2. /* 创建一个递归互斥量用于保护共享资源 */
  3. xMutex = xSemaphoreCreateRecursiveMutex();
  4. }
  5. void vAnotherTask( void * pvParameters )
  6. {
  7. /* 其他功能代码 */
  8. if ( xMutex != NULL ) {
  9. /* 尝试获取递归互斥量,如果不可用则等待 10 个 ticks */
  10. if(xSemaphoreTakeRecursive(xMutex,( TickType_t ) 10 )== pdTRUE) {
  11. /* 获取到递归信号量,可以访问共享资源 */
  12. /* ... 其他功能代码 */
  13. /* 重复获取递归互斥量 */
  14. xSemaphoreTakeRecursive( xMutex, ( TickType_t ) 10 );
  15. xSemaphoreTakeRecursive( xMutex, ( TickType_t ) 10 );
  16. /* 释放递归互斥量,获取了多少次就要释放多少次 */
  17. xSemaphoreGiveRecursive( xMutex );
  18. xSemaphoreGiveRecursive( xMutex );
  19. xSemaphoreGiveRecursive( xMutex );
  20. /* 现在递归互斥量可以被其他任务获取 */
  21. } else {
  22. /* 没能成功获取互斥量,所以不能安全的访问共享资源 */
  23. }
  24. }
  25. }

19.7 函数调用关系总结

19.7.1 创建

19.5-6 互斥量函数接口详解 - 图4

19.7.2 获取

19.5-6 互斥量函数接口详解 - 图5

19.9.3 释放

19.5-6 互斥量函数接口详解 - 图6