FreeRTOS-06-訊號量

說明

本文僅作為學習FreeRTOS的記錄文檔,作為初學者肯定很多理解不對甚至錯誤的地方,望網友指正。
FreeRTOS是一個RTOS(實時作業系統)系統,支援搶佔式、合作式和時間片調度。適用於微處理器或小型微處理器的實時應用。
本文檔使用的FreeRTOS版本:FreeRTOS Kernel V10.4.1
參考文檔:《FreeRTOS_Reference_Manual_V10.0.0.pdf》《FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf》《STM32F4 FreeRTOS開發手冊_V1.1.pdf》
參考影片:正點原子FreeRTOS手把手教學-基於STM32_嗶哩嗶哩_bilibili

9 訊號量

訊號量是作業系統重要的一部分,一般用來進行資源管理和任務同步,FreeRTOS訊號量分為二值訊號量、計數型訊號量、互斥訊號量和遞歸型訊號量。

9.1 二值訊號量

9.1.1 相關說明

二值訊號量通常用於互斥訪問或者同步。和互斥訊號量的差別:互斥訊號量有優先順序繼承,二值訊號量沒有優先順序繼承,所有二值訊號量適用於同步,而互斥訊號量適用於互斥訪問。

和隊列一樣,訊號量API函數允許設置一個阻塞時間,阻塞時間是當任務獲得訊號的時候由於訊號量無效從而導致任務進行阻塞態的最大時鐘節拍數,當訊號量有效時高優先順序任務就會解除阻塞狀態。

二值訊號量就是只有一個隊列項的隊列,所以這個隊列要麼滿的,要麼空的。二值訊號量的工作過程如下:

(1)二值訊號量無效,任務阻塞等待訊號量

image-20210925202005105

(2)中斷髮生,釋放了訊號量

image-20210925202021317

(3)任務獲取訊號量成功,從阻塞狀態解除

image-20210925202037726

(4)任務再次進入阻塞態,等待訊號量

image-20210925202146779

完整的一次流程如下:

img

9.1.2 相關函數

(1)動態創建訊號量

函數原型:

#include "FreeRTOS.h"
#include "semphr.h"
SemaphoreHandle_t xSemaphoreCreateBinary( void );

函數描述:創建一個二值訊號量,並返回訊號量句柄。每一個訊號量需要一個記憶體空間來存放訊號量狀態。這個函數的創建的訊號量空間由FreeRTOS自動分配。訊號量創建之後是空的,任務這時候是無法獲得的。

函數參數:

返回值:NULL:創建失敗。其他值:創建成功的二值訊號量的句柄

(2)靜態創建訊號量

函數原型:

#include 「FreeRTOS.h」
#include 「semphr.h」
SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t *pxSemaphoreBuffer );

函數描述:創建一個二值訊號量,並返回訊號量句柄。每一個訊號量需要一個記憶體空間來存放訊號量狀態。這個函數的創建的訊號量空間由用戶指定。訊號量創建之後是空的,任務這時候是無法獲得的。

函數參數:pxSemaphoreBuffer:指向StaticSemaphore_t類型的變數,這個變數用來保存訊號量的狀態。

返回值:NULL:創建失敗。其他值:創建成功的二值訊號量的句柄

(3)任務級釋放訊號量

函數原型:

#include 「FreeRTOS.h」
#include 「semphr.h」
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );

函數描述:釋放訊號量之前,訊號量必須已經被創建了。

函數參數:xSemaphore:要釋放的訊號量句柄

返回值:pdPASS:訊號量釋放成功。pdFAIL:訊號量釋放失敗。

(4)中斷級釋放訊號量

函數原型:

#include 「FreeRTOS.h」
#include 「semphr.h」
BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore,
                                 BaseType_t *pxHigherPriorityTaskWoken );

函數描述:中斷級釋放訊號量函數,在中斷處理函數中使用。

函數參數:xSemaphore:要釋放的訊號量句柄;

pxHigherPriorityTaskWoken:標記退出此函數是否進行任務切換,此值為pdTRUE的時候在退出中斷函數之前要進行一次任務切換。

返回值:pdPASS:訊號量釋放成功。errQUEUE_FULL:訊號量釋放失敗,訊號量已經被釋放了。

(5)任務級獲取訊號量

函數原型:

#include 「FreeRTOS.h」
#include 「semphr.h」
BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore,
                          TickType_t xTicksToWait );

函數描述:獲取訊號量之前,訊號量必須已經被創建了。

函數參數:xSemaphore:要獲取的訊號量句柄;

xTicksToWait:當任務無法獲取到訊號量,任務最大的保持阻塞的時間。如果為0,任務無法獲得訊號量時將立即返回。阻塞時間指的時時鐘滴答數,所以阻塞的時間大小取決於系統頻率,可以使用pdMS_TO_TICKS() 宏來指定阻塞多少毫秒。如果為portMAX_DELAY,任務將一直等待。

返回值:pdPASS:訊號量獲取成功。pdFAIL:訊號量獲取失敗。

(6)中斷級獲取訊號量

函數原型:

#include 「FreeRTOS.h」
#include 「queue.h」
BaseType_t xSemaphoreTakeFromISR( SemaphoreHandle_t xSemaphore,
                                 signed BaseType_t *pxHigherPriorityTaskWoken );

函數描述:在中斷服務函數中獲取訊號量。

函數參數:xSemaphore:要獲取的訊號量句柄;

pxHigherPriorityTaskWoken:標記退出此函數後是否進行任務切換,此值為pdTRUE的時候在退出中斷函數之前要進行一次任務切換。

返回值:pdPASS:訊號量獲取成功。pdFAIL:訊號量獲取失敗。

9.1.3 操作實驗

實驗目的:使用二值訊號量完成任務間的同步

實驗設計:任務task00定時釋放二值訊號量,任務task01獲取二值訊號量,接收到訊號量就進行相應的動作。

測試程式碼:

/* task00 info */
configSTACK_DEPTH_TYPE Task00_STACK_SIZE = 5;
UBaseType_t  Task00_Priority = 1;
TaskHandle_t Task00_xHandle;

/* task01 info */
configSTACK_DEPTH_TYPE Task01_STACK_SIZE = 5;
UBaseType_t  Task01_Priority = 2;
TaskHandle_t Task01_xHandle;

//二值訊號量
SemaphoreHandle_t BinarySemaphore;

void vTask00_Code(void *para)
{
    static unsigned int cnt = 0;
    BaseType_t err = pdTRUE;
    for (;;)
    {
        PRINT(" task00 cnt %u...", cnt++);
        err = xSemaphoreGive(BinarySemaphore);
        if (err != pdTRUE)
            PRINT("BinarySemaphore give failed!\n");
        vTaskDelay(2000);
    }
}

void vTask01_Code(void *para)
{
    static unsigned int cnt = 0;
    BaseType_t err = pdTRUE;
    for (;;)
    {
        xSemaphoreTake(BinarySemaphore, portMAX_DELAY);
        PRINT(" task01 cnt %u...", cnt++);
        vTaskDelay(500);
    }
}

void test_BinarySemaphore()
{
    BinarySemaphore = xSemaphoreCreateBinary();
    
    if (xTaskCreate(vTask00_Code, "task00 task", 
        Task00_STACK_SIZE, NULL, Task00_Priority,
        &Task00_xHandle) != pdPASS)
    {
        PRINT("creat task00 failed!\n");
    }
    
    if (xTaskCreate(vTask01_Code, "task01 task", 
        Task01_STACK_SIZE, NULL, Task01_Priority,
        &Task01_xHandle) != pdPASS)
    {
        PRINT("creat task01 failed!\n");
    }
}

void creat_task(void)
{
    test_BinarySemaphore();
//    test_queue();
}

編譯,運行:

$ ./build/freertos-simulator 
 task00 cnt 0...
 task01 cnt 0...
 task00 cnt 1...
 task01 cnt 1...
 task00 cnt 2...
 task01 cnt 2...

可以看出,任務task01等到了訊號量之後才會執行。

接著,將獲取訊號量函數xSemaphoreTake的阻塞時間改為0,也就是沒獲取到訊號量,立即返回。

void vTask01_Code(void *para)
{
    static unsigned int cnt = 0;
    BaseType_t err = pdTRUE;
    for (;;)
    {
        xSemaphoreTake(BinarySemaphore, 0);
        PRINT(" task01 cnt %u...", cnt++);
        vTaskDelay(500);
    }
}

編譯,運行:

$ ./build/freertos-simulator 
 task01 cnt 0...
 task00 cnt 0...
 task01 cnt 1...
 task01 cnt 2...
 task01 cnt 3...
 task01 cnt 4...
 task00 cnt 1...

可以看出,任務task01不會等待訊號量,而是繼續執行。

9.1.4 優先順序反轉

使用二值訊號量會出現優先順序反轉的問題,優先順序反轉在可剝奪內核中是常見的,但在實時系統中不允許出現這種現象。

image-20210925222858088

上述圖中,高任務H會晚於低優先順序任務M執行,這就發生了優先順序反轉。

優先順序反轉實驗設計:

實驗設計:創建三個任務,高優先順序任務獲取二值訊號量,獲取成功後進行相應的處理,處理完之後釋放訊號量;中優先順序任務簡單運行;低優先順序任務和高優先順序任務一樣,會獲取二值訊號量,獲取成功後進行相應處理。

測試程式碼:

configSTACK_DEPTH_TYPE TaskLow_STACK_SIZE = 5;
UBaseType_t  TaskLow_Priority = 2;
TaskHandle_t TaskLow_xHandle;

configSTACK_DEPTH_TYPE TaskMiddle_STACK_SIZE = 5;
UBaseType_t  TaskMiddle_Priority = 3;
TaskHandle_t TaskMiddle_xHandle;

configSTACK_DEPTH_TYPE TaskHigh_STACK_SIZE = 5;
UBaseType_t  TaskHigh_Priority = 4;
TaskHandle_t TaskHigh_xHandle;


//二值訊號量
SemaphoreHandle_t BinarySemaphore;

void vTaskLow_Code(void *para)
{
    static unsigned int times = 0;
    BaseType_t err = pdTRUE;
    for (;;)
    {
        xSemaphoreTake(BinarySemaphore, portMAX_DELAY);
        PRINT(" low task running");
        for (times = 0; times < 20000000; times++)
            taskYIELD();
        err = xSemaphoreGive(BinarySemaphore);
        if (err != pdTRUE)
            PRINT("BinarySemaphore give failed!");
        vTaskDelay(1000);
    }
}

void vTaskMiddle_Code(void *para)
{
    for (;;)
    {
        PRINT(" task middle running");
        vTaskDelay(1000);
    }
}

void vTaskHigh_Code(void *para)
{
    for (;;)
    {
        vTaskDelay(500);
        PRINT(" task high Pend Sem");
        xSemaphoreTake(BinarySemaphore, portMAX_DELAY);
        PRINT(" task high running!");
        xSemaphoreGive(BinarySemaphore);
        vTaskDelay(500);
    }
}


void test_BinarySemaphore()
{
    taskENTER_CRITICAL();
    BinarySemaphore = xSemaphoreCreateBinary();
    if (BinarySemaphore != NULL)
        xSemaphoreGive(BinarySemaphore);
    
    if (xTaskCreate(vTaskLow_Code, "taskLow task", 
        TaskLow_STACK_SIZE, NULL, TaskLow_Priority,
        &TaskLow_xHandle) != pdPASS)
    {
        PRINT("creat taskLow failed!\n");
    }
    
    if (xTaskCreate(vTaskMiddle_Code, "taskMiddle task", 
        TaskMiddle_STACK_SIZE, NULL, TaskMiddle_Priority,
        &TaskMiddle_xHandle) != pdPASS)
    {
        PRINT("creat taskMiddle failed!\n");
    }

    if (xTaskCreate(vTaskHigh_Code, "taskHigh task", 
        TaskHigh_STACK_SIZE, NULL, TaskHigh_Priority,
        &TaskHigh_xHandle) != pdPASS)
    {
        PRINT("creat taskHigh failed!\n");
    }
    taskEXIT_CRITICAL();
}


void creat_task(void)
{
    test_BinarySemaphore();
}

編譯、運行:

$ ./build/freertos-simulator 
 task middle running
 low task running
 task high Pend Sem
 task middle running
 task middle running
 task middle running
 task middle running
 task middle running
 task middle running
 task middle running
 task middle running
 task middle running
 task middle running
 task middle running
 task middle running
 task middle running
 task middle running
 task middle running
 task middle running
 task middle running
 task middle running
 task middle running
 task middle running
 task middle running
 task middle running
 task middle running
 task middle running
 task middle running
 task middle running
 task middle running
 task middle running
 task high running!
 task middle running
 task high Pend Sem
 task high running!
 low task running
 task middle running

可以看出了優先順序反轉,中優先順序任務比高優先順序任務先執行。

9.2 計數型訊號量

計數型訊號量也叫數值型訊號量,其實質是長度大於1的隊列。

主要用於兩個場景:

1、事件計數:在這個場景中,每次事件發生時就在事件處理函數中釋放訊號量,其他任務獲取訊號量來處理事件。這種場合計數型訊號量初始計數值為0。

2、資源管理:在這個場景中,訊號量代表當前可用的資源數量。一個任務想要獲取資源的使用權,必須先獲得訊號量,訊號量獲取成功訊號量就會減1,當訊號量為0時就沒有訊號量了。當一個任務使用完訊號量之後要釋放訊號量。這個場景中,訊號量的初始值就是資源的數量。

9.2.1 相關函數

(1)動態創建函數

函數原型:

#include 「FreeRTOS.h」
#include 「semphr.h」
SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount,
                                           UBaseType_t uxInitialCount );

函數描述:創建一個計數型訊號量,返回訊號量的句柄。訊號量的記憶體空間由系統指定。

函數參數:uxMaxCount:計數訊號量的最大計數值,當訊號量值等於這個值的時候釋放訊號量就會失敗。

uxInitialCount:計數訊號量初始值。

返回值:NULL:計數訊號量創建失敗;其他值:計數訊號量創建成功,返回計數訊號量句柄。

(2)靜態創建函數

函數原型:

#include 「FreeRTOS.h」
#include 「semphr.h」
SemaphoreHandle_t xSemaphoreCreateCountingStatic( UBaseType_t uxMaxCount,
                                                 UBaseType_t uxInitialCount,
                                                 StaticSemaphore_t pxSempahoreBuffer );

函數描述:創建一個計數型訊號量,返回訊號量的句柄。訊號量的記憶體空間由用戶指定。

函數參數:uxMaxCount:計數訊號量的最大計數值,當訊號量值等於這個值的時候釋放訊號量就會失敗。

uxInitialCount:計數訊號量初始值。

pxSempahoreBuffer:指向StaticSemaphore_t類型的變數,用於保存訊號量結構體。

返回值:NULL:計數訊號量創建失敗;其他值:計數訊號量創建成功,返回計數訊號量句柄。

(2)釋放和獲取函數

釋放和獲取函數和二值訊號量的一樣,參見9.1.2小節。

9.2.2 操作實驗

實驗目的:學習計數型訊號量的使用方法。

實驗設計:主函數中創建一個計數型訊號量,計數值為10,初始化計數值為0,然後創建兩個任務,任務task00釋放訊號量,任務task01獲取訊號量。

測試程式碼:

/* task00 info */
configSTACK_DEPTH_TYPE Task00_STACK_SIZE = 5;
UBaseType_t  Task00_Priority = 1;
TaskHandle_t Task00_xHandle;

/* task01 info */
configSTACK_DEPTH_TYPE Task01_STACK_SIZE = 5;
UBaseType_t  Task01_Priority = 3;
TaskHandle_t Task01_xHandle;

//計數型訊號量
SemaphoreHandle_t CountSemaphore;

void vTask00_Code(void *para)
{
    static unsigned int cnt = 0;
    BaseType_t err = pdTRUE;
    for (;;)
    {
        PRINT(" task00 cnt %u...", cnt++);
        err = xSemaphoreGive(CountSemaphore);
        if (err != pdTRUE)
            PRINT("BinarySemaphore give failed!\n");
        vTaskDelay(200);
        if (cnt == 4)
            vTaskDelete(Task00_xHandle);
    }
}

void vTask01_Code(void *para)
{
    static unsigned int cnt = 0;
    BaseType_t err = pdTRUE;
    for (;;)
    {
        xSemaphoreTake(CountSemaphore, portMAX_DELAY);
        PRINT(" task01 cnt %u...", cnt++);
        vTaskDelay(1000);
    }
}

void test_CountSemaphore()
{
    CountSemaphore = xSemaphoreCreateCounting(10, 0);
    
    if (xTaskCreate(vTask00_Code, "task00 task", 
        Task00_STACK_SIZE, NULL, Task00_Priority,
        &Task00_xHandle) != pdPASS)
    {
        PRINT("creat task00 failed!\n");
    }
    
    if (xTaskCreate(vTask01_Code, "task01 task", 
        Task01_STACK_SIZE, NULL, Task01_Priority,
        &Task01_xHandle) != pdPASS)
    {
        PRINT("creat task01 failed!\n");
    }
}


void creat_task(void)
{
    test_CountSemaphore();
}

編譯、運行:

$ ./build/freertos-simulator 
 task00 cnt 0...
 task01 cnt 0...
 task00 cnt 1...
 task00 cnt 2...
 task00 cnt 3...
 task01 cnt 1...
 task01 cnt 2...
 task01 cnt 3...

可以看出,共釋放了4個訊號量,任務task1執行了4次。

Tags: