The Queue in real-time operating systems (RTOS) such as FreeRTOS is a very important communication mechanism, mainly used for 1. Inter-Process Communication (IPC) and data transfer between tasks and interrupts.2. It can perform task synchronization and resource management. General queues can protect and utilize resources by implementing semaphores and mutexes.This article, as the second part of the queue module, will focus on how queues implement task synchronization and resource management.1. How Queues Implement Task Synchronization and Resource ManagementIn the previous article, “FreeRTOS Queue Module (Part 1)”, we learned that queues have a blocking mechanism when sending/receiving data:
- Send Blocking When the queue is full, the sending task can voluntarily suspend.
- Receive Blocking When the queue is empty, the receiving task can voluntarily suspend.
- Wake-up Mechanism Automatically wakes up waiting tasks when conditions are met.
Based on this mechanism, we can implement binary semaphores/counting semaphores/mutexes through queues to achieve task synchronization and resource management.1. Binary Semaphore: It can implement simple event notifications between two tasks or between interrupts and tasks, thus helping to synchronize tasks (as a synchronization flag). Essentially, it is a sequence of length 1, with an item size of 0, which can only represent values 1 or 0, indicating the availability and occupation of the semaphore, respectively.For a typical application scenario:
// Interrupt service routine gives a semaphorevoid vISR_Handler() { BaseType_t xHigherPriorityTaskWoken = pdFALSE; xSemaphoreGiveFromISR(xBinarySem, &xHigherPriorityTaskWoken); portYIELD_FROM_ISR(xHigherPriorityTaskWoken);}// Task waiting for semaphorevoid vTask() { while(1) { if(xSemaphoreTake(xBinarySem, portMAX_DELAY) == pdTRUE) { // Process interrupt event } }}
After the interrupt task is completed, it gives the semaphore, and the task realizes this and starts processing the interrupt event.2. Counting Semaphore: Similar to the implementation principle of binary semaphores, but more complex and powerful. It is essentially a sequence of length greater than 1, with an item size of 0. It can have values from 0 up to the user-defined uxlength (maximum count value). Each give increases the count, and each take decreases the count, blocking when it reaches 0. It indicates the amount of available resources.For a typical application scenario:
// Essentially a queue with length >1, item size of 0QueueHandle_t xCountingSem = xQueueCreate(10, 0); // Maximum count 10// Resource pool managementvoid vResourceUser() { if(xSemaphoreTake(xCountingSem, pdMS_TO_TICKS(100))) { // Acquire resource use_resource(); // Release resource xSemaphoreGive(xCountingSem); }}// Event countingvoid vEventHandler() { for(int i=0; i < 5; i++) xSemaphoreGive(xCountingSem); // Record 5 events}
Releasing resources increases the semaphore, consuming resources decreases the semaphore, and when the semaphore is 0, it enters a blocking state.
3. Mutex
Mutexes are very similar to binary semaphores. It is a binary semaphore with special markings, so it can also protect critical sections by setting and clearing to ensure that only one task accesses shared resources at the same time, thus protecting shared resources. The difference from binary semaphores is that it records the task holding the critical section resource. Resources can only be released by the holder, and apart from that, resources can be recursively acquired/released, thus meeting the needs of multiple tasks accessing the critical section. The priority inversion function discussed earlier in task management will also be called when the waiting task’s priority is higher than the running task’s priority, automatically elevating the holder’s priority.For a typical scenario:
// Special marked binary semaphoreQueueHandle_t xMutex = xQueueCreateMutex(queueQUEUE_IS_MUTEX);// Protect shared resourcevoid vTaskA() { xSemaphoreTake(xMutex, portMAX_DELAY); // Access critical section xSemaphoreGive(xMutex);}// Recursive lock usagevoid vTaskB() { xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY); // Call a function that may take the lock again vNestedFunction(); xSemaphoreGiveRecursive(xRecursiveMutex);}void vNestedFunction() { xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY); // Operate shared resource xSemaphoreGiveRecursive(xRecursiveMutex);}
Mutexes protect critical sections and orderly arrange the use of critical sections through recursive calls (allowing the same task to acquire the lock multiple times).
In addition, there are resource pools, read-write locks, etc., to implement task synchronization and resource management methods. Due to space limitations, these will not be covered here.Next, I will explain how FreeRTOS implements the above functions in detail by referring to the source code:2. Detailed Code Analysis of Semaphores<span>1. Dynamically create semaphore <span>xQueueCreateCountingSemaphore</span> ()</span>
QueueHandle_t xQueueCreateCountingSemaphore( const UBaseType_t uxMaxCount, const UBaseType_t uxInitialCount ) { QueueHandle_t xHandle; configASSERT( uxMaxCount != 0 ); configASSERT( uxInitialCount <= uxMaxCount ); xHandle = xQueueGenericCreate( uxMaxCount, queueSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_COUNTING_SEMAPHORE ); if( xHandle != NULL ) { ( ( Queue_t * ) xHandle )->uxMessagesWaiting = uxInitialCount; traceCREATE_COUNTING_SEMAPHORE(); } else { traceCREATE_COUNTING_SEMAPHORE_FAILED(); } return xHandle; }
Semaphores are essentially a generic sequence, but there are some proprietary functions. Binary/counting semaphores share a semaphore creation function (the difference is whether uxMaxCount is 1).
The core steps of this function involve calling a function to create a generic queue, just with some parameter handling.
xHandle = xQueueGenericCreate( uxMaxCount, queueSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_COUNTING_SEMAPHORE );
<span>uxMaxCount as the queue length (i.e., the maximum count value of the semaphore).</span><span>queueSEMAPHORE_QUEUE_ITEM_LENGTH = 0, because semaphores do not store actual data.</span><span>queueQUEUE_TYPE_COUNTING_SEMAPHORE indicates that this is a counting semaphore.</span>
In addition, we need to prepare a count value (the amount of available resources).
( ( Queue_t * ) xHandle )->uxMessagesWaiting = uxInitialCount;
- Calling
<span>xQueueGive()</span><span> will increase the count value (equivalent to </span><code><span>give</span>the semaphore). - Calling
<span>xQueueTake()</span><span> will decrease the count value (equivalent to </span><code><span>take</span>the semaphore).
2. Statically create semaphore xQueueCreateCountingSemaphoreStatic ()
QueueHandle_t xQueueCreateCountingSemaphoreStatic( const UBaseType_t uxMaxCount, const UBaseType_t uxInitialCount, StaticQueue_t * pxStaticQueue ) { QueueHandle_t xHandle; configASSERT( uxMaxCount != 0 ); configASSERT( uxInitialCount <= uxMaxCount ); xHandle = xQueueGenericCreateStatic( uxMaxCount, queueSEMAPHORE_QUEUE_ITEM_LENGTH, NULL, pxStaticQueue, queueQUEUE_TYPE_COUNTING_SEMAPHORE ); if( xHandle != NULL ) { ( ( Queue_t * ) xHandle )->uxMessagesWaiting = uxInitialCount; traceCREATE_COUNTING_SEMAPHORE(); } else { traceCREATE_COUNTING_SEMAPHORE_FAILED(); } return xHandle; }
<span><span>xQueueCreateCountingSemaphoreStatic is used for</span></span>statically creating counting semaphores. It requires the user to pre-allocate a <span><span>StaticQueue_t</span></span> structure, which does not rely on dynamic memory. It is suitable forembedded systems without dynamic memory or high reliability. Other operations (<span><span>Give</span></span>/<span><span>Take</span></span>) are exactly the same as the dynamic version.3. Acquire semaphore xQueueSemaphoreTake ()
BaseType_t xQueueSemaphoreTake( QueueHandle_t xQueue, TickType_t xTicksToWait ){ BaseType_t xEntryTimeSet = pdFALSE; TimeOut_t xTimeOut; Queue_t * const pxQueue = xQueue; #if ( configUSE_MUTEXES == 1 ) BaseType_t xInheritanceOccurred = pdFALSE; #endif /* Check the queue pointer is not NULL. */ configASSERT( ( pxQueue ) ); /* Check this really is a semaphore, in which case the item size will be * 0. */ configASSERT( pxQueue->uxItemSize == 0 ); /* Cannot block if the scheduler is suspended. */ #if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) ) { configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) ); } #endif /*lint -save -e904 This function relaxes the coding standard somewhat to allow return * statements within the function itself. This is done in the interest * of execution time efficiency. */ for( ; ; ) { taskENTER_CRITICAL(); { /* Semaphores are queues with an item size of 0, and where the * number of messages in the queue is the semaphore's count value. */ const UBaseType_t uxSemaphoreCount = pxQueue->uxMessagesWaiting; /* Is there data in the queue now? To be running the calling task * must be the highest priority task wanting to access the queue. */ if( uxSemaphoreCount > ( UBaseType_t ) 0 ) { traceQUEUE_RECEIVE( pxQueue ); /* Semaphores are queues with a data size of zero and where the * messages waiting is the semaphore's count. Reduce the count. */ pxQueue->uxMessagesWaiting = uxSemaphoreCount - ( UBaseType_t ) 1; #if ( configUSE_MUTEXES == 1 ) { if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ) { /* Record the information required to implement * priority inheritance should it become necessary. */ pxQueue->u.xSemaphore.xMutexHolder = pvTaskIncrementMutexHeldCount(); } else { mtCOVERAGE_TEST_MARKER(); } } #endif /* configUSE_MUTEXES */ /* Check to see if other tasks are blocked waiting to give the * semaphore, and if so, unblock the highest priority such task. */ if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE ) { if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE ) { queueYIELD_IF_USING_PREEMPTION(); } else { mtCOVERAGE_TEST_MARKER(); } } else { mtCOVERAGE_TEST_MARKER(); } taskEXIT_CRITICAL(); return pdPASS; } else { if( xTicksToWait == ( TickType_t ) 0 ) { /* For inheritance to have occurred there must have been an * initial timeout, and an adjusted timeout cannot become 0, as * if it were 0 the function would have exited. */ #if ( configUSE_MUTEXES == 1 ) { configASSERT( xInheritanceOccurred == pdFALSE ); } #endif /* configUSE_MUTEXES */ /* The semaphore count was 0 and no block time is specified * (or the block time has expired) so exit now. */ taskEXIT_CRITICAL(); traceQUEUE_RECEIVE_FAILED( pxQueue ); return errQUEUE_EMPTY; } else if( xEntryTimeSet == pdFALSE ) { /* The semaphore count was 0 and a block time was specified * so configure the timeout structure ready to block. */ vTaskInternalSetTimeOutState( &xTimeOut ); xEntryTimeSet = pdTRUE; } else { /* Entry time was already set. */ mtCOVERAGE_TEST_MARKER(); } } } taskEXIT_CRITICAL(); /* Interrupts and other tasks can give to and take from the semaphore * now the critical section has been exited. */ vTaskSuspendAll(); prvLockQueue( pxQueue ); /* Update the timeout state to see if it has expired yet. */ if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE ) { /* A block time is specified and not expired. If the semaphore * count is 0 then enter the Blocked state to wait for a semaphore to * become available. As semaphores are implemented with queues the * queue being empty is equivalent to the semaphore count being 0. */ if( prvIsQueueEmpty( pxQueue ) != pdFALSE ) { traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue ); #if ( configUSE_MUTEXES == 1 ) { if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ) { taskENTER_CRITICAL(); { xInheritanceOccurred = xTaskPriorityInherit( pxQueue->u.xSemaphore.xMutexHolder ); } taskEXIT_CRITICAL(); } else { mtCOVERAGE_TEST_MARKER(); } } #endif /* if ( configUSE_MUTEXES == 1 ) */ vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait ); prvUnlockQueue( pxQueue ); if( xTaskResumeAll() == pdFALSE ) { portYIELD_WITHIN_API(); } else { mtCOVERAGE_TEST_MARKER(); } } else { /* There was no timeout and the semaphore count was not 0, so * attempt to take the semaphore again. */ prvUnlockQueue( pxQueue ); ( void ) xTaskResumeAll(); } } else { /* Timed out. */ prvUnlockQueue( pxQueue ); ( void ) xTaskResumeAll(); /* If the semaphore count is 0 exit now as the timeout has * expired. Otherwise return to attempt to take the semaphore that is * known to be available. As semaphores are implemented by queues the * queue being empty is equivalent to the semaphore count being 0. */ if( prvIsQueueEmpty( pxQueue ) != pdFALSE ) { #if ( configUSE_MUTEXES == 1 ) { /* xInheritanceOccurred could only have be set if * pxQueue->uxQueueType == queueQUEUE_IS_MUTEX so no need to * test the mutex type again to check it is actually a mutex. */ if( xInheritanceOccurred != pdFALSE ) { taskENTER_CRITICAL(); { UBaseType_t uxHighestWaitingPriority; /* This task blocking on the mutex caused another * task to inherit this task's priority. Now this task * has timed out the priority should be disinherited * again, but only as low as the next highest priority * task that is waiting for the same mutex. */ uxHighestWaitingPriority = prvGetDisinheritPriorityAfterTimeout( pxQueue ); vTaskPriorityDisinheritAfterTimeout( pxQueue->u.xSemaphore.xMutexHolder, uxHighestWaitingPriority ); } taskEXIT_CRITICAL(); } } #endif /* configUSE_MUTEXES */ traceQUEUE_RECEIVE_FAILED( pxQueue ); return errQUEUE_EMPTY; } else { mtCOVERAGE_TEST_MARKER(); } } } /*lint -restore */}
This function is used toacquire a semaphore, which can be a binary semaphore, counting semaphore, or mutex (Mutex). If the semaphore is available (count value > 0), it is directly acquired and returns success. If the semaphore is unavailable (count value = 0), it decides whether to block based on <span><span>xTicksToWait</span></span>.
If the semaphore is available (uxSemaphoreCount > 0) it directly acquires the semaphore and directly decreases the count value (<span><span>uxMessagesWaiting--</span></span>).If it is a mutex, it records the holder. If the current task is blocked due to acquiring the mutex, the task holding the mutex will temporarily elevate its priority.
// If it is a mutex, record the holder (priority inheritance) #if (configUSE_MUTEXES == 1) if (pxQueue->uxQueueType == queueQUEUE_IS_MUTEX) { pxQueue->u.xSemaphore.xMutexHolder = pvTaskIncrementMutexHeldCount(); } #endif
At the same time, this function will wake up tasks waiting to release the semaphore and may trigger a task switch.If the semaphore equals 0, meaning it is unavailable, it will enter the blocking logic. The task will be added to the <span><span>xTasksWaitingToReceive</span></span> list. When other tasks call <span><span>xQueueGive</span></span><span>, it will wake up the waiting tasks.</span><span><span>4. Release semaphore <span>xQueueGenericSend</span> ()</span></span><pre><code>This function was mentioned in the previous article, but since it is quite long, it will not be included here.In the previous article, this function was referred to as a generic function for sending data to the queue. In fact, in the simplified FreeRTOS code, this function includes the role of the <span>xQueueSemaphoreGive()</span> function, which is to release the semaphore.The key to this function in completing the task is calling the function <span>prvCopyDataToQueue();</span>
static BaseType_t prvCopyDataToQueue( Queue_t * const pxQueue, const void * pvItemToQueue, const BaseType_t xPosition ){ BaseType_t xReturn = pdFALSE; UBaseType_t uxMessagesWaiting; /* This function is called from a critical section. */ uxMessagesWaiting = pxQueue->uxMessagesWaiting; if( pxQueue->uxItemSize == ( UBaseType_t ) 0 ) { #if ( configUSE_MUTEXES == 1 ) { if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ) { /* The mutex is no longer being held. */ xReturn = xTaskPriorityDisinherit( pxQueue->u.xSemaphore.xMutexHolder ); pxQueue->u.xSemaphore.xMutexHolder = NULL; } else { mtCOVERAGE_TEST_MARKER(); } } #endif /* configUSE_MUTEXES */ } else if( xPosition == queueSEND_TO_BACK ) { ( void ) memcpy( ( void * ) pxQueue->pcWriteTo, pvItemToQueue, ( size_t ) pxQueue->uxItemSize ); /*lint !e961 !e418 !e9087 MISRA exception as the casts are only redundant for some ports, plus previous logic ensures a null pointer can only be passed to memcpy() if the copy size is 0. Cast to void required by function signature and safe as no alignment requirement and copy length specified in bytes. */ pxQueue->pcWriteTo += pxQueue->uxItemSize; /*lint !e9016 Pointer arithmetic on char types ok, especially in this use case where it is the clearest way of conveying intent. */ if( pxQueue->pcWriteTo >= pxQueue->u.xQueue.pcTail ) /*lint !e946 MISRA exception justified as comparison of pointers is the cleanest solution. */ { pxQueue->pcWriteTo = pxQueue->pcHead; } else { mtCOVERAGE_TEST_MARKER(); } } else { ( void ) memcpy( ( void * ) pxQueue->u.xQueue.pcReadFrom, pvItemToQueue, ( size_t ) pxQueue->uxItemSize ); /*lint !e961 !e9087 !e418 MISRA exception as the casts are only redundant for some ports. Cast to void required by function signature and safe as no alignment requirement and copy length specified in bytes. Assert checks null pointer only used when length is 0. */ pxQueue->u.xQueue.pcReadFrom -= pxQueue->uxItemSize; if( pxQueue->u.xQueue.pcReadFrom < pxQueue->pcHead ) /*lint !e946 MISRA exception justified as comparison of pointers is the cleanest solution. */ { pxQueue->u.xQueue.pcReadFrom = ( pxQueue->u.xQueue.pcTail - pxQueue->uxItemSize ); } else { mtCOVERAGE_TEST_MARKER(); } if( xPosition == queueOVERWRITE ) { if( uxMessagesWaiting > ( UBaseType_t ) 0 ) { /* An item is not being added but overwritten, so subtract * one from the recorded number of items in the queue so when * one is added again below the number of recorded items remains * correct. */ --uxMessagesWaiting; } else { mtCOVERAGE_TEST_MARKER(); } } else { mtCOVERAGE_TEST_MARKER(); } } pxQueue->uxMessagesWaiting = uxMessagesWaiting + ( UBaseType_t ) 1; return xReturn;}
Parameter Description
| Parameter | Type | Description |
|---|---|---|
<span>pxQueue</span> |
<span>Queue_t*</span> |
Pointer to the queue control block, containing the metadata of the queue. |
<span>pvItemToQueue</span> |
<span>const void*</span> |
Pointer to the data to be written (for semaphore operations, it is <span>NULL</span>). |
<span>xPosition</span> |
<span>BaseType_t</span> |
Write position:– <span>queueSEND_TO_BACK</span><span> (to the back)</span><span>- </span><code><span>queueSEND_TO_FRONT</span><span> (to the front)</span><span>- </span><code><span>queueOVERWRITE</span><span> (overwrite mode)</span> |
This function discusses ordinary queues/semaphores/mutexes based on the size of Queue_t->uxItemSize and processes them differently.
(1) Get Current Message Count
uxMessagesWaiting = pxQueue->uxMessagesWaiting;
Read the current message count of the queue (the count value of the semaphore).
(2) Process Semaphore/Mutex (<span><span>uxItemSize = 0</span></span>)
if(pxQueue->uxItemSize == (UBaseType_t))
The <span>uxItemSize = 0</span> for semaphores/mutexes, which do not store actual data.
Process Mutex
#if(configUSE_MUTEXES == 1)if(pxQueue->uxQueueType == queueQUEUE_IS_MUTEX) { xReturn = xTaskPriorityDisinherit(pxQueue->u.xSemaphore.xMutexHolder); pxQueue->u.xSemaphore.xMutexHolder =NULL;}#endif
If the current queue is a mutex (<span><span>uxQueueType == queueQUEUE_IS_MUTEX</span></span>), release the lock and process priority inheritance:<span><span>xReturn indicates whether a task switch is needed (if the priority changes).</span></span>
Process Semaphore
else{mtCOVERAGE_TEST_MARKER(); // No actual operation, only for coverage testing}
Semaphores only need to increase the count value (subsequent logic processing).
(3) Process Ordinary Queue (<span><span>uxItemSize > 0</span></span>)
Data Write to Back (<span><span>queueSEND_TO_BACK</span></span>)
else if(xPosition == queueSEND_TO_BACK){memcpy(pxQueue->pcWriteTo, pvItemToQueue, pxQueue->uxItemSize);pxQueue->pcWriteTo += pxQueue->uxItemSize;if(pxQueue->pcWriteTo >= pxQueue->u.xQueue.pcTail) {pxQueue->pcWriteTo = pxQueue->pcHead; // Circular queue processing }}
1. Copy data to the <span><span>pcWriteTo</span></span> position.
2. Move Write Pointer <span><span>pcWriteTo</span></span>.
3. If out of bounds, reset to the queue head (circular queue).
Data Write to Front (<span><span>queueSEND_TO_FRONT</span></span>)
else{memcpy(pxQueue->u.xQueue.pcReadFrom, pvItemToQueue, pxQueue->uxItemSize);pxQueue->u.xQueue.pcReadFrom -= pxQueue->uxItemSize;if(pxQueue->u.xQueue.pcReadFrom < pxQueue->pcHead){ pxQueue->u.xQueue.pcReadFrom = (pxQueue->u.xQueue.pcTail - pxQueue->uxItemSize);}}
- Copy data to the
<span><span>pcReadFrom</span></span>position (queue head). - Move Read Pointer
<span><span>pcReadFrom</span></span>(reverse move). - If out of bounds, reset to the queue tail.
(4) Update Message Count
pxQueue->uxMessagesWaiting = uxMessagesWaiting +1;
All types of queues (semaphores, queues, overwrite mode) will increase <span><span>uxMessagesWaiting</span></span>. Semaphores: count value +1.Queues: message count +1.Overwrite mode: since it was previously reduced by 1, it remains unchanged.The above functions are the core functions of semaphores, and the most important are the semaphore’s give and take functions, which work around the count value and wake-up service. To illustrate with a diagram:
3. Detailed Code Analysis of Mutexes1.xQueueCreateMutex() Mutex Creation
QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType ) { QueueHandle_t xNewQueue; const UBaseType_t uxMutexLength = ( UBaseType_t ) 1, uxMutexSize = ( UBaseType_t ) 0; xNewQueue = xQueueGenericCreate( uxMutexLength, uxMutexSize, ucQueueType ); prvInitialiseMutex( ( Queue_t * ) xNewQueue ); return xNewQueue; }
The core logic of this function is very simple. First, it calls <span><span>xQueueGenericCreate</span></span> to create a queue (length 1, size 0, actually used as a mutex), and then calls <span><span>prvInitialiseMutex</span></span> to initialize the special properties of the mutex.
Therefore, the key to creating a mutex lies in its initialization function.
2.prvInitialiseMutex () Mutex Initialization
static void prvInitialiseMutex( Queue_t * pxNewQueue ) { if( pxNewQueue != NULL ) { /* The queue create function will set all the queue structure members * correctly for a generic queue, but this function is creating a * mutex. Overwrite those members that need to be set differently - * in particular the information required for priority inheritance. */ pxNewQueue->u.xSemaphore.xMutexHolder = NULL; pxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX; /* In case this is a recursive mutex. */ pxNewQueue->u.xSemaphore.uxRecursiveCallCount = 0; traceCREATE_MUTEX( pxNewQueue ); /* Start with the semaphore in the expected state. */ ( void ) xQueueGenericSend( pxNewQueue, NULL, ( TickType_t ) 0U, queueSEND_TO_BACK ); } else { traceCREATE_MUTEX_FAILED(); } }
This function checks the pointer’s validity and then performs the following four important steps:
pxNewQueue->u.xSemaphore.xMutexHolder = NULL; pxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX; /* In case this is a recursive mutex. */ pxNewQueue->u.xSemaphore.uxRecursiveCallCount = 0; /* Start with the semaphore in the expected state. */ ( void ) xQueueGenericSend( pxNewQueue, NULL, ( TickType_t ) 0U, queueSEND_TO_BACK );
(1) Set the mutex holder to NULL, indicating that no task currently holds the lock.
(2) Mark the queue type as a mutex, setting uxQueueType to mutex type, allowing other functions to process according to mutex markings. In FreeRTOS, queues, semaphores, and mutexes share the same data structure (<span>Queue_t</span>
), distinguished by <span>uxQueueType</span> behavior:<span><span>queueQUEUE_IS_MUTEX: Mutex (supports priority inheritance).</span></span><span><span>queueQUEUE_IS_COUNTING_SEMAPHORE: Counting semaphore.</span></span>Other values: Ordinary queue.
(3) Initialize the recursive counter u.xSemaphore.uxRecursiveCallCount to 0, used to support recursive mutexes. Recursion means that if the same task calls <span>xSemaphoreTakeRecursive</span>, the counter will increase by 1 each time it acquires the lock, and decrease by 1 when released, only truly releasing the lock when the counter returns to zero.
(4) Send an unlock signal by sending an empty message through xQueueGenericSend to indicate that the mutex is available (the queue is not empty).
3.xQueueGetMutexHolder () Get Mutex Holder Handle
TaskHandle_t xQueueGetMutexHolder( QueueHandle_t xSemaphore ) { TaskHandle_t pxReturn; Queue_t * const pxSemaphore = ( Queue_t * ) xSemaphore; /* This function is called by xSemaphoreGetMutexHolder(), and should not * be called directly. Note: This is a good way of determining if the * calling task is the mutex holder, but not a good way of determining the * identity of the mutex holder, as the holder may change between the * following critical section exiting and the function returning. */ taskENTER_CRITICAL(); { if( pxSemaphore->uxQueueType == queueQUEUE_IS_MUTEX ) { pxReturn = pxSemaphore->u.xSemaphore.xMutexHolder; } else { pxReturn = NULL; } } taskEXIT_CRITICAL(); return pxReturn; }
This function is used internally in FreeRTOS toget the current task handle holding the mutex, usually called by <span><span>xSemaphoreGetMutexHolder()</span></span>. Its main function is toread the mutex holder information safely and handle race conditions. (Requires critical section protection).
if (pxSemaphore->uxQueueType == queueQUEUE_IS_MUTEX) { pxReturn = pxSemaphore->u.xSemaphore.xMutexHolder;} else { pxReturn = NULL;}
The core logic:
- Check the
<span>uxQueueType</span>field (queue type) to confirm that the passed handle is indeed a mutex, not an ordinary queue or semaphore. - If it is a mutex, return the current holder
<span>xMutexHolder</span>; otherwise return<span>NULL</span>.
This function also has an interrupt call version that can be called instantly from an interrupt. Since this function is non-modifying but accessing, it does not require disabling interrupts.
4.xQueueGiveMutexRecursive () Release Mutex
BaseType_t xQueueGiveMutexRecursive( QueueHandle_t xMutex ) { BaseType_t xReturn; Queue_t * const pxMutex = ( Queue_t * ) xMutex; configASSERT( pxMutex ); /* If this is the task that holds the mutex then xMutexHolder will not * change outside of this task. If this task does not hold the mutex then * pxMutexHolder can never coincidentally equal the tasks handle, and as * this is the only condition we are interested in it does not matter if * pxMutexHolder is accessed simultaneously by another task. Therefore no * mutual exclusion is required to test the pxMutexHolder variable. */ if( pxMutex->u.xSemaphore.xMutexHolder == xTaskGetCurrentTaskHandle() ) { traceGIVE_MUTEX_RECURSIVE( pxMutex ); /* uxRecursiveCallCount cannot be zero if xMutexHolder is equal to * the task handle, therefore no underflow check is required. Also, * uxRecursiveCallCount is only modified by the mutex holder, and as * there can only be one, no mutual exclusion is required to modify the * uxRecursiveCallCount member. */ ( pxMutex->u.xSemaphore.uxRecursiveCallCount )--; /* Has the recursive call count unwound to 0? */ if( pxMutex->u.xSemaphore.uxRecursiveCallCount == ( UBaseType_t ) 0 ) { /* Return the mutex. This will automatically unblock any other * task that might be waiting to access the mutex. */ ( void ) xQueueGenericSend( pxMutex, NULL, queueMUTEX_GIVE_BLOCK_TIME, queueSEND_TO_BACK ); } else { mtCOVERAGE_TEST_MARKER(); } xReturn = pdPASS; } else { /* The mutex cannot be given because the calling task is not the * holder. */ xReturn = pdFAIL; traceGIVE_MUTEX_RECURSIVE_FAILED( pxMutex ); } return xReturn; }
This function is the core function used in FreeRTOS torecursively release a mutex. Its main function is to check whether the current task holds the lock (otherwise return failure); decrease the recursive count (<span><span>uxRecursiveCallCount</span></span>); when the counter returns to zero, truly release the lock (allowing other tasks to acquire it).
This function first checks whether the current task is the holder. If the current task is the holder, then <span>xMutexHolder</span> will not be modified by other tasks (because only the holder can release the lock). If the current task is not the holder, even if other tasks concurrently modify <span>xMutexHolder</span>, it will not be misjudged (because it only cares whether it matches the current task).
If it can be released, then decrease the recursive counter
(pxMutex->u.xSemaphore.uxRecursiveCallCount)--;
No protection required: Only the holder can modify this field, so there are no race conditions.
Check whether the counter value returns to zero
if (pxMutex->u.xSemaphore.uxRecursiveCallCount == 0)
If the counter returns to zero, it indicates that all recursively acquired locks have been released, and it is necessary totruly release the lock. Call <span>xQueueGenericSend</span><span> to send a "available" signal.</span>
(void)xQueueGenericSend(pxMutex, NULL, queueMUTEX_GIVE_BLOCK_TIME, queueSEND_TO_BACK);
5. Mutex Acquisition xQueueTakeMutexRecursive ()
BaseType_t xQueueTakeMutexRecursive( QueueHandle_t xMutex, TickType_t xTicksToWait ) { BaseType_t xReturn; Queue_t * const pxMutex = ( Queue_t * ) xMutex; configASSERT( pxMutex ); /* Comments regarding mutual exclusion as per those within * xQueueGiveMutexRecursive(). */ traceTAKE_MUTEX_RECURSIVE( pxMutex ); if( pxMutex->u.xSemaphore.xMutexHolder == xTaskGetCurrentTaskHandle() ) { ( pxMutex->u.xSemaphore.uxRecursiveCallCount )++; xReturn = pdPASS; } else { xReturn = xQueueSemaphoreTake( pxMutex, xTicksToWait ); /* pdPASS will only be returned if the mutex was successfully * obtained. The calling task may have entered the Blocked state * before reaching here. */ if( xReturn != pdFAIL ) { ( pxMutex->u.xSemaphore.uxRecursiveCallCount )++; } else { traceTAKE_MUTEX_RECURSIVE_FAILED( pxMutex ); } } return xReturn; }
This function is the core function used in FreeRTOS torecursively acquire a mutex. Its main function is to check whether the current task already holds the lock (if so, only increase the recursive counter); if not, attempt to acquire the lock (which may block waiting); supports nested lock acquisition (the same task can acquire the same lock multiple times).
This function checks whether the current task holds the lock (as in the previous function). If the current task is already the holder of the lock, simply increase the recursive counter <span>uxRecursiveCallCount</span>. (No blocking or critical section protection is needed, because only the holder can modify this field.)
If the current task does not hold the lock, it will attempt to call <span><span>xQueueSemaphoreTake</span></span><span> to acquire the lock:</span>
else { xReturn = xQueueSemaphoreTake(pxMutex, xTicksToWait); // Block to acquire the lock if (xReturn != pdFAIL) { (pxMutex->u.xSemaphore.uxRecursiveCallCount)++; // After successful acquisition, counter +1 } else { traceTAKE_MUTEX_RECURSIVE_FAILED(pxMutex); // Record acquisition failure (for debugging) }}
- Call
<span><span>xQueueSemaphoreTake</span></span><span> to attempt to acquire the lock: </span><span>If the lock is available: return </span><code><span>pdPASS</span>immediately. If the lock is held by another task: decide whether to block or timeout based on<span><span>xTicksToWait</span></span>. - After successful acquisition: set the holder to the current task (completed in
<span>xQueueSemaphoreTake</span>). Initialize the recursive counter to 1 (since this is the first acquisition). - When acquisition fails: return
<span>pdFAIL</span>(e.g., timeout or invalid handle). Optionally record debugging information (<span>traceTAKE_MUTEX_RECURSIVE_FAILED</span>).
Overall, the core functions of mutexes have the following three points:
- Protect Shared Resources Ensure that only one task can access the critical section (such as global variables, hardware peripherals) at the same time.
- Prevent Priority Inversion Temporarily elevate the priority of the low-priority task holding the lock through the priority inheritance mechanism.
- Support Recursive Locking Allow the same task to acquire the same lock multiple times (must be paired with release).
Mutexes do not require critical section protection because they record the holder and can only be called by the holder. When recursively using mutexes, the number of releases needs to match the number of acquisitions to fully release; otherwise, it may lead to deadlock. I will summarize the working mechanism of mutexes with a diagram:

4. Summary of the Entire Article
The queues in FreeRTOS are not only the infrastructure for Inter-Process Communication (IPC), but also through blocking/wake-up mechanisms and empty/full state management, derive the following synchronization and resource management functions:
- Semaphore:
- Binary Semaphore A queue of length 1, item size 0, simulating a boolean flag (0/1), used for event notification.
- Counting Semaphore A queue of length >1, item size 0, indicating the number of available resources (such as buffer pools).
- A specially marked binary semaphore that supports priority inheritance and recursive locking, protecting shared resources.
Comparing:
| Feature | Queue | Semaphore | Mutex |
|---|---|---|---|
| Data Storage | Stores actual data items | Only counts (no data) | Only counts (no data) |
| Blocking Mechanism | Based on empty/full state | Based on count value (0 blocks) | Based on holder state |
| Holder Concept | No | No | Yes (must be released by holder) |
| Priority Inheritance | Not supported | Not supported | Supported |
| Recursive Locking | Not supported | Not supported | Supported |
| Typical Use | Data transfer between tasks | Event notification/resource pool | Shared resource protection |