Signals and Semaphores in FreeRTOS

Signals and Semaphores in FreeRTOS

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.

Signals and Semaphores in FreeRTOS

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. 1.

    Prefer using task notifications over binary semaphores.

  2. 2.

    Use counting semaphores for resource management.

  3. 3.

    Use mutexes for shared resource access.

  4. 4.

    Avoid blocking calls in ISR.

  5. 5.

    Always check the return values of API functions.

  6. 6.

    Set reasonable timeout values for synchronization operations.

  7. 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.

Leave a Comment