Scan to FollowLearn Embedded Together, learn and grow together

The FreeRTOS introductory series aims to help beginners quickly get started and master the basic principles and usage of FreeRTOS while organizing knowledge.
FreeRTOS Quick Start – Exploring the System
FreeRTOS Official Chinese Website is Now Live
FreeRTOS Coding Standards and Data Types
FreeRTOS Quick Start – Task Management
FreeRTOS Learning – Detailed Explanation of Message Queues
FreeRTOS Learning – Counting Semaphores
FreeRTOS Learning – Mutex Semaphores
FreeRTOS Learning – Event Groups
FreeRTOS Learning – Task Notifications
FreeRTOS Learning – Software Timers
FreeRTOS Learning – Memory Management
This article introduces the resource management related content of FreeRTOS.
As a popular Real-Time Operating System (RTOS), FreeRTOS’s resource management mechanism is crucial for system stability and reliability.
Resource management mainly involves access control to shared resources between tasks, including the allocation and usage of memory, peripherals, and data.
In a multitasking environment, when multiple tasks access shared resources simultaneously without control, it may lead to data inconsistency, system crashes, and other issues. FreeRTOS provides various resource management mechanisms to avoid these problems:
- Critical Section Protection
- Mutex
- Binary Semaphore
- Counting Semaphore
- Recursive Mutex
- Reader-Writer Lock (partial implementation)
Critical Section Protection
2.1 Basic Concepts
A critical section refers to a code segment that accesses shared resources, which must ensure exclusive access during its execution.
FreeRTOS provides two methods for critical section protection:
// Enter critical section (disable all interrupts)
taskENTER_CRITICAL();
// Code accessing shared resources
// Exit critical section (restore interrupts)
taskEXIT_CRITICAL();
// Nested critical section version
taskENTER_CRITICAL_FROM_ISR();
taskEXIT_CRITICAL_FROM_ISR();
Features and Considerations
- The strictest protection mechanism, disables interrupts
- Critical section code should be kept as short as possible to avoid affecting system real-time performance
- Do not call APIs that may block tasks within a critical section
- Nested calls are safe
Mutex
Basics of Mutex
A mutex is a special type of binary semaphore used to implement mutual access to resources, featuring a priority inheritance mechanism.
// Create mutex
SemaphoreHandle_t xMutex = xSemaphoreCreateMutex();
// Take mutex
if(xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE)
{
// Access shared resources
// Release mutex
xSemaphoreGive(xMutex);
}
Priority Inheritance Mechanism
When a high-priority task is blocked waiting for a mutex held by a low-priority task, the low-priority task temporarily inherits the high-priority task’s priority to reduce the impact of priority inversion issues.
Usage Scenarios
- Protect shared resources that require long operations
- Resource access may involve task blocking
- Scenarios that require automatic resolution of priority inversion
Binary Semaphore
Basic Usage
A binary semaphore can be used for task synchronization or simple mutual exclusion control, but it does not have the priority inheritance feature.
// Create binary semaphore
SemaphoreHandle_t xBinarySemaphore = xSemaphoreCreateBinary();
// Give semaphore
xSemaphoreGive(xBinarySemaphore);
// Take semaphore
if(xSemaphoreTake(xBinarySemaphore, portMAX_DELAY) == pdTRUE) {
// Execute protected code
}
Differences from Mutex
- Binary semaphore does not record the holder, while mutex does
- Binary semaphore lacks a priority inheritance mechanism
- Binary semaphore is more suitable for event notifications, while mutex is better for resource protection
Counting Semaphore
Basic Concepts
A counting semaphore is used to manage a limited number of resources, with its value representing the number of available resources.
// Create counting semaphore, initial value is the maximum number of resources
SemaphoreHandle_t xCountingSemaphore = xSemaphoreCreateCounting(MAX_RESOURCES, MAX_RESOURCES);
// Take resource
if(xSemaphoreTake(xCountingSemaphore, portMAX_DELAY) == pdTRUE)
{
// Use resource
// Release resource
xSemaphoreGive(xCountingSemaphore);
}
Typical Applications
- Manage a limited number of hardware resources (e.g., DMA channels)
- Buffer management in producer-consumer problems
- Limit the number of concurrent accesses
Recursive Mutex
6.1 Concept of Recursive Locks
Allows the same task to acquire the same mutex multiple times without causing deadlock.
// Create recursive mutex
SemaphoreHandle_t xRecursiveMutex = xSemaphoreCreateRecursiveMutex();
// Recursive take
xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY);
xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY);
// Recursive give
xSemaphoreGiveRecursive(xRecursiveMutex);
xSemaphoreGiveRecursive(xRecursiveMutex);
Usage Scenarios
- Functions that may be called recursively need to access shared resources
- A task needs to enter the same critical section multiple times
Reader-Writer Lock (Partial Implementation)
Some FreeRTOS port versions or extensions provide a reader-writer lock mechanism, allowing multiple readers or a single writer to access resources.
// Create read-write lock
SemaphoreHandle_t xReadWriteLock = xSemaphoreCreateReadWrite();
// Take read lock
xSemaphoreTakeRead(xReadWriteLock, portMAX_DELAY);
// Multiple readers can hold the read lock simultaneously
xSemaphoreGiveRead(xReadWriteLock);
// Take write lock
xSemaphoreTakeWrite(xReadWriteLock, portMAX_DELAY);
// Only one writer is allowed
xSemaphoreGiveWrite(xReadWriteLock);
Usage of Resource Management
- Minimize Critical Sections: Keep critical sections as short as possible
- Avoid Nesting: Minimize the levels of nested locks
- Fixed Order: Acquire multiple locks in a fixed order to avoid deadlocks
- Timeout Settings: Set reasonable timeouts for lock operations to avoid permanent blocking
- Priority Design: Design task priorities reasonably to reduce priority inversion
- Resource Layering: Layer resource management to reduce complexity
Common Problems and Solutions
Priority Inversion Problem
Phenomenon: Medium-priority tasks preempt low-priority tasks, causing high-priority tasks to be blocked
Solution:
- Use mutex instead of binary semaphore
- Design task priorities reasonably
- Consider using priority ceiling protocol (supported by some implementations)
Deadlock Problem
Phenomenon: Multiple tasks wait for each other to hold resources
Solution:
- Acquire multiple locks in a fixed order
- Use lock timeout mechanisms
- Design lock-free algorithms (e.g., RCU)
Resource Exhaustion Problem
Phenomenon: Semaphore count is exhausted, tasks cannot acquire resources
Solution:
- Set initial resource quantity reasonably
- Implement resource recovery mechanisms
- Add resource monitoring and alerts
FreeRTOS Resource Management API Reference
| API Function | Description |
|---|---|
<span>vPortEnterCritical()</span> |
Enter critical section |
<span>vPortExitCritical()</span> |
Exit critical section |
<span>xSemaphoreCreateMutex()</span> |
Create mutex |
<span>xSemaphoreCreateBinary()</span> |
Create binary semaphore |
<span>xSemaphoreCreateCounting()</span> |
Create counting semaphore |
<span>xSemaphoreCreateRecursiveMutex()</span> |
Create recursive mutex |
<span>xSemaphoreTake()</span> |
Take semaphore |
<span>xSemaphoreGive()</span> |
Give semaphore |
<span>xSemaphoreTakeRecursive()</span> |
Recursive take |
<span>xSemaphoreGiveRecursive()</span> |
Recursive give |
<span>uxSemaphoreGetCount()</span> |
Get semaphore count value |
Application Examples
Multitasking Access to Shared Peripherals
// Protect shared UART resource
SemaphoreHandle_t xUartMutex;
void UART_Init()
{
xUartMutex = xSemaphoreCreateMutex();
}
void Task1(void *pvParameters)
{
while(1)
{
if(xSemaphoreTake(xUartMutex, pdMS_TO_TICKS(100)) == pdTRUE)
{
printf("Task1 using UART\n");
xSemaphoreGive(xUartMutex);
}
vTaskDelay(pdMS_TO_TICKS(500));
}
}
void Task2(void *pvParameters)
{
while(1)
{
if(xSemaphoreTake(xUartMutex, pdMS_TO_TICKS(100)) == pdTRUE)
{
printf("Task2 using UART\n");
xSemaphoreGive(xUartMutex);
}
vTaskDelay(pdMS_TO_TICKS(500));
}
}
Producer-Consumer Model
// Use counting semaphore to manage buffer
SemaphoreHandle_t xEmptySlots;
SemaphoreHandle_t xFilledSlots;
QueueHandle_t xBufferQueue;
void ProducerTask(void *pvParameters)
{
int data = 0;
while(1)
{
// Wait for empty slots
xSemaphoreTake(xEmptySlots, portMAX_DELAY);
// Produce data and put it in the queue
xQueueSend(xBufferQueue, &data, 0);
data++;
// Notify consumer of new data
xSemaphoreGive(xFilledSlots);
}
}
void ConsumerTask(void *pvParameters)
{
int receivedData;
while(1)
{
// Wait for data
xSemaphoreTake(xFilledSlots, portMAX_DELAY);
// Get data from the queue
xQueueReceive(xBufferQueue, &receivedData, 0);
printf("Consumed: %d\n", receivedData);
// Notify producer of empty slot
xSemaphoreGive(xEmptySlots);
}
}
Conclusion
FreeRTOS provides a rich set of resource management mechanisms, and developers should choose the appropriate methods based on specific application scenarios:
- Simple Shared Variables: Critical section protection
- Short-term Resource Access: Mutex
- Event Notification: Binary semaphore
- Multiple Similar Resources: Counting semaphore
- Recursive Access: Recursive mutex
- Read-Mostly Scenarios: Reader-writer lock (if supported)
Proper use of these mechanisms can build stable and efficient embedded real-time systems, avoiding resource conflicts, deadlocks, priority inversions, and ensuring reliable system operation.

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