Scan to followLearn Embedded Together, learn and grow together

A mutex semaphore (Mutex, short for Mutual Exclusion) is a special type of binary semaphore in FreeRTOS, specifically designed for implementing mutual access to resources.
Compared to ordinary semaphores, mutex semaphores have the following key characteristics:
- Ownership Concept: Only the task that takes the mutex can give it back
- Priority Inheritance Mechanism: Prevents priority inversion issues
- Recursive Acquisition: The same task can take the same mutex multiple times (requires configuration)
Working Principle
Basic Workflow
- Task Takes Mutex: Gains exclusive access to shared resources
- Task Uses Resource: Safely accesses shared resources
- Task Gives Mutex: Allows other tasks to access the resource
Priority Inheritance Mechanism
When a low-priority task holds a mutex and a high-priority task attempts to take it:
- The low-priority task temporarily inherits the priority of the high-priority task
- The low-priority task completes execution faster and releases the mutex
- After releasing, it returns to its original priority
This mechanism effectively reduces the duration of priority inversion.
Main APIs for Mutex Semaphores
Create Mutex
SemaphoreHandle_t xSemaphoreCreateMutex(void);
- Return Value: Returns the mutex handle on success, NULL on failure
- The created mutex is initially available
Example:
SemaphoreHandle_t xMutex = xSemaphoreCreateMutex();
if(xMutex == NULL) {
// Error handling
}
Create Recursive Mutex
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex(void);
- Allows the same task to take the same mutex multiple times
- Must be released the same number of times to truly release it
Take Mutex
Normal Mutex:
BaseType_t xSemaphoreTake(SemaphoreHandle_t xMutex, TickType_t xTicksToWait);
Recursive Mutex:
BaseType_t xSemaphoreTakeRecursive(SemaphoreHandle_t xMutex, TickType_t xTicksToWait);
Parameters:
<span>xMutex</span>: Mutex handle<span>xTicksToWait</span>: Maximum wait time (portMAX_DELAY means wait indefinitely)
Return Value:
- pdTRUE: Successfully taken
- pdFALSE: Timeout or error
Release Mutex
Normal Mutex:
BaseType_t xSemaphoreGive(SemaphoreHandle_t xMutex);
Recursive Mutex:
BaseType_t xSemaphoreGiveRecursive(SemaphoreHandle_t xMutex);
Parameters: Mutex handle
Return Value:
- pdTRUE: Successfully released
- pdFALSE: Error (e.g., task does not hold the mutex)
Application Scenarios
Shared Resource Protection
Protect exclusive access to hardware peripherals (e.g., UART, SPI) or software resources (e.g., global variables, data structures).
Critical Section Protection
Protect short critical section code instead of disabling interrupts, improving system responsiveness.
Thread-Safe Data Structures
Implement thread-safe access to data structures like linked lists and queues.
Examples
Basic Usage Example
SemaphoreHandle_t xUARTMutex;
void vTask1(void *pvParameters) {
while(1) {
// Take UART mutex
if(xSemaphoreTake(xUARTMutex, portMAX_DELAY) == pdTRUE) {
// Exclusive access to UART
printf("Task1 using UART\n");
vTaskDelay(pdMS_TO_TICKS(100));
// Release mutex
xSemaphoreGive(xUARTMutex);
}
vTaskDelay(1);
}
}
void vTask2(void *pvParameters) {
while(1) {
// Take UART mutex (wait up to 10ms)
if(xSemaphoreTake(xUARTMutex, pdMS_TO_TICKS(10)) == pdTRUE) {
// Exclusive access to UART
printf("Task2 using UART\n");
vTaskDelay(pdMS_TO_TICKS(50));
// Release mutex
xSemaphoreGive(xUARTMutex);
} else {
printf("Task2 failed to get UART\n");
}
vTaskDelay(1);
}
}
int main(void) {
// Create mutex
xUARTMutex = xSemaphoreCreateMutex();
// Create tasks
xTaskCreate(vTask1, "Task1", configMINIMAL_STACK_SIZE, NULL, 2, NULL);
xTaskCreate(vTask2, "Task2", configMINIMAL_STACK_SIZE, NULL, 2, NULL);
// Start scheduler
vTaskStartScheduler();
while(1);
}
Recursive Mutex Example
SemaphoreHandle_t xRecursiveMutex;
void vRecursiveFunction(int depth) {
// Recursively take mutex
if(xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY) != pdTRUE) {
return;
}
printf("Depth %d - Mutex acquired\n", depth);
if(depth < 3) {
vRecursiveFunction(depth + 1); // Recursive call
}
// Recursively release mutex
xSemaphoreGiveRecursive(xRecursiveMutex);
printf("Depth %d - Mutex released\n", depth);
}
void vRecursiveTask(void *pvParameters) {
while(1) {
vRecursiveFunction(0);
vTaskDelay(1000);
}
}
int main(void) {
// Create recursive mutex
xRecursiveMutex = xSemaphoreCreateRecursiveMutex();
// Create task
xTaskCreate(vRecursiveTask, "RecTask", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
// Start scheduler
vTaskStartScheduler();
while(1);
}
Mutex Semaphores vs Binary Semaphores
| Feature | Mutex Semaphore | Binary Semaphore |
|---|---|---|
| Ownership | Must be released by the holder | Any task can release |
| Priority Inheritance | Supported | Not supported |
| Recursive Acquisition | Configurable support | Not supported |
| Initial State | Available immediately after creation | Configurable initial state |
| Usage Scenario | Resource protection | Task synchronization/event notification |
| On Release | Only the holder can release | Any task can release |
Considerations
Usage Principles
- Keep Hold Time Short: Minimize the time holding the mutex
- Avoid Nested Acquisition: Avoid multiple acquisitions unless using a recursive mutex
- Consistent Acquisition Order: Acquire multiple mutexes in a fixed order to prevent deadlocks
- Always Check Return Values: Especially for timed acquisition operations
Error Handling
-
Handling Acquisition Failures:
if(xSemaphoreTake(xMutex, pdMS_TO_TICKS(100)) != pdTRUE) { // Handle timeout or error } -
Handling Release Failures:
if(xSemaphoreGive(xMutex) != pdTRUE) { // Usually indicates the task does not hold the mutex }
Performance Considerations
- Avoid Using in ISR: Mutexes are designed for inter-task communication; ISRs should use semaphores
- Optimize Short Critical Sections: For very short critical sections, consider suspending the task scheduler or disabling interrupts
- Evaluate Alternatives: For simple variable protection, consider using atomic operations
Common Issues and Solutions
Deadlock Issues
Scenario:
- Task A holds mutex X and requests mutex Y
- Task B holds mutex Y and requests mutex X
Solution:
- Fix the mutex acquisition order
- Use timed acquisition operations
- Avoid cross-acquisition in design
Priority Inversion
Scenario:
- Low-priority task holds a mutex
- Medium-priority task preempts CPU
- High-priority task waits for the mutex
Solution:
- Ensure priority inheritance is enabled (default support in FreeRTOS mutexes)
- Design task priorities reasonably
- Reduce mutex hold time
Resource Leaks
Scenario: Task is deleted before releasing the mutex
Solution:
- Ensure all held mutexes are released before deleting the task
- Use resource tracking mechanisms
- Consider using RAII patterns (e.g., in C++)
Advanced
Implementation Principles of Mutexes
FreeRTOS mutexes internally use the following mechanisms:
- Task blocking list: Manages tasks waiting for the mutex
- Holder tracking: Records the current task holding the mutex
- Priority adjustment: Implements priority inheritance
Comparison with Task Notifications
For simple resource protection, task notifications may be more efficient:
- Less memory usage
- Faster operations
- But does not support multiple tasks waiting
Cross-Platform Considerations
Different RTOS implementations of mutexes may vary:
- Recursive behavior
- Details of priority inheritance
- Error handling methods
Conclusion
FreeRTOS mutex semaphores are a key mechanism for protecting shared resources. Proper usage can:
- Ensure data consistency
- Prevent race conditions
- Resolve priority inversion issues
Key points:
- Prefer using mutexes over binary semaphores for resource protection
- Keep critical section code as short as possible
- Always check API return values
- Design task priorities and resource access order reasonably

Follow 【Learn Embedded Together】 to become better together。
If you find this article useful, click “Share”, “Like”, or “Recommend”!