
1. Overview of Signals and Semaphores
In FreeRTOS, signals and semaphores are both important mechanisms for inter-task communication (IPC), but they serve different purposes and operate differently.
1.1 Signals
- •
In FreeRTOS, signals refer to task notifications, which are a lightweight signaling mechanism.
- •
Each task has a 32-bit notification value.
- •
Can be used as a substitute for event groups, binary semaphores, etc.
- •
More efficient, consuming less memory.
1.2 Semaphores
- •
A more traditional synchronization mechanism.
- •
Divided into binary semaphores, counting semaphores, and mutex semaphores.
- •
Used for task synchronization and resource management.
2. Detailed Explanation of Task Notifications (Signals)
2.1 Advantages of Task Notifications
- •
45% faster than semaphores (according to official FreeRTOS tests).
- •
Minimal memory usage (no need to create additional kernel objects).
- •
Highly flexible, can carry data.
2.2 Task Notification API
BaseType_t xTaskNotifyGive(TaskHandle_t xTaskToNotify);void vTaskNotifyGiveFromISR(TaskHandle_t xTaskToNotify, BaseType_t *pxHigherPriorityTaskWoken);
uint32_t ulTaskNotifyTake(BaseType_t xClearCountOnExit, TickType_t xTicksToWait);
BaseType_t xTaskNotify(TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction);BaseType_t xTaskNotifyFromISR(TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, BaseType_t *pxHigherPriorityTaskWoken);
BaseType_t xTaskNotifyWait(uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait);
2.3 Task Notification Usage Patterns
Lightweight Binary Semaphore Replacement
// Send notification (equivalent to give semaphore)
xTaskNotifyGive(xTaskHandle);
// Receive notification (equivalent to take semaphore)
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
Lightweight Counting Semaphore Replacement
// Send multiple notifications
for(int i=0; i<5; i++) {
xTaskNotifyGive(xTaskHandle);
}
// Receive notification (accumulated count)
uint32_t count = ulTaskNotifyTake(pdFALSE, portMAX_DELAY);// count will be 5
Data Transmission
// Send data
xTaskNotify(xTaskHandle, 0xABCD, eSetValueWithOverwrite);
// Receive data
uint32_t value;
xTaskNotifyWait(0, 0, &value, portMAX_DELAY);// value will be 0xABCD
3. Detailed Explanation of Semaphores
3.1 Types of Semaphores
Binary Semaphore
- •
Only two states: 0 and 1.
- •
Commonly used for task synchronization.
Counting Semaphore
- •
Can have values greater than 1.
- •
Commonly used for resource management.
Mutex Semaphore (Mutex)
- •
A special binary semaphore with priority inheritance mechanism.
- •
Used to protect shared resources.
3.2 Semaphore API
// Create semaphore
SemaphoreHandle_t xSemaphoreCreateBinary(void);
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount);
SemaphoreHandle_t xSemaphoreCreateMutex(void);
// Take semaphore
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);
BaseType_t xSemaphoreTakeFromISR(SemaphoreHandle_t xSemaphore, BaseType_t *pxHigherPriorityTaskWoken);
// Give semaphore
BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore);
BaseType_t xSemaphoreGiveFromISR(SemaphoreHandle_t xSemaphore, BaseType_t *pxHigherPriorityTaskWoken);
3.3 Semaphore Usage Examples
Binary Semaphore Synchronization
SemaphoreHandle_t xBinarySemaphore;
void vTask1(void *pvParameters) {
// Perform some operations
xSemaphoreGive(xBinarySemaphore); // Send signal
}
void vTask2(void *pvParameters) {
xSemaphoreTake(xBinarySemaphore, portMAX_DELAY); // Wait for signal
// Continue execution
}
Counting Semaphore for Resource Management
SemaphoreHandle_t xCountingSemaphore;
void init() {
// Create counting semaphore, indicating 3 resources available
xCountingSemaphore = xSemaphoreCreateCounting(3, 3);
}
void vTask(void *pvParameters) {
if(xSemaphoreTake(xCountingSemaphore, pdMS_TO_TICKS(100)) == pdTRUE) {
// Use resource
xSemaphoreGive(xCountingSemaphore); // Release resource
}
}
4. Choosing Between Signals and Semaphores
4.1 Scenarios for Using Task Notifications (Signals)
- •
One-to-one communication.
- •
Need for high-performance lightweight synchronization.
- •
Memory-constrained environments.
- •
Need to pass simple data.
4.2 Scenarios for Using Semaphores
- •
Many-to-one or many-to-many communication.
- •
Need for a broadcast mechanism (in conjunction with event groups).
- •
Need for more complex synchronization patterns.
- •
Need for resource counting management.
5. Advanced Topics
5.1 Priority Inversion and Mutex Semaphores
- •
Mutex semaphores have a priority inheritance mechanism.
- •
When a low-priority task holds a mutex, a medium-priority task will block a high-priority task.
- •
FreeRTOS mutexes automatically elevate the priority of the holder.
5.2 Recursive Mutexes
SemaphoreHandle_t xRecursiveMutex = xSemaphoreCreateRecursiveMutex();
// Tasks can acquire the same recursive mutex multiple times
xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY);
xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY);
// Must release the same number of times
xSemaphoreGiveRecursive(xRecursiveMutex);
xSemaphoreGiveRecursive(xRecursiveMutex);
5.3 Deadlock Prevention
- •
Avoid multiple tasks acquiring multiple mutexes in different orders.
- •
Use timeout mechanisms to avoid permanent blocking.
- •
Design a clear resource acquisition order.

6. Performance Comparison
|
Mechanism |
Speed |
Memory Overhead |
Flexibility |
|---|---|---|---|
|
Task Notifications |
Fastest |
Minimal |
One-to-one |
|
Binary Semaphores |
Medium |
Medium |
Many-to-many |
|
Counting Semaphores |
Medium |
Medium |
Resource Management |
|
Mutexes |
Slower |
Medium |
Resource Sharing |
7. Best Practices
- 1.
Prefer using task notifications over binary semaphores.
- 2.
Use counting semaphores for resource management.
- 3.
Use mutexes for shared resource access.
- 4.
Avoid blocking calls in ISR.
- 5.
Always check the return values of API functions.
- 6.
Set reasonable timeout values for synchronization operations.
- 7.
Consider using static allocation to create semaphores (using functions like xSemaphoreCreateBinaryStatic).
8. Common Problem Solutions
8.1 Unable to Acquire Semaphore
- •
Check if any task forgot to release the semaphore.
- •
Use timeout mechanisms to avoid permanent blocking.
- •
Consider using xSemaphoreGetMutexHolder to debug mutexes.
8.2 Task Notification Loss
- •
Use eSetValueWithOverwrite or eSetValueWithoutOverwrite based on needs.
- •
Consider using eIncrement action instead of directly setting values.
8.3 Priority Inversion Issues
- •
Ensure to use mutexes instead of binary semaphores to protect shared resources.
- •
Check if task priority settings are reasonable.