【freertos】003-任務基礎知識

李柱明發表於2022-03-28

前言

資源:

任務概念

程式:程式是程式執行的過程,是程式在執行過程中分配和管理資源的基本單位。擁有獨立的虛擬地址空間。

執行緒:執行緒是CPU排程和分派的基本單位。與其它同一程式的執行緒共享當前程式資源。

協程:比執行緒更加輕量級的存在,不是由作業系統核心管理,而是由程式控制的。其實就是在同一執行緒內時分地執行不同的子程式。(注意:不是函式呼叫)

還有管程、纖程。

併發:多個任務看起來是同時進行, 這是一種假並行。

並行:並行是指令同一時刻一起執行。

對於目前主流的RTOS的任務,大部分都屬於併發的執行緒。

因為MCU上的資源每個任務都是共享的,可以認為是單程式多執行緒模型。

任務狀態

freertos有四種狀態,每種狀態都有對應的狀態連結串列管理。

執行態:佔用CPU使用權時的狀態。

就緒態:能夠執行(沒有被阻塞和掛起),但是當前沒有執行的任務的狀態。

阻塞態:由於等待訊號量、訊息佇列、事件標誌組、呼叫延遲函式等而處於的狀態被稱之為阻塞態。

掛起態:呼叫函式vTaskSuspend()對指定任務進行掛起,掛起後這個任務將不被執行。

  • 呼叫函式xTaskResume()可退出掛起狀態。
  • 不可以指定超時週期事件(不可以通過設定超時事件而退出掛起狀態)

任務狀態轉換圖:

任務優先順序

每個任務被分配一個從0到(configMAX_PRIORITIES - 1)的優先順序。

configMAX_PRIORITIES 是在 FreeRTOSConfig.h檔案中被定義。

優先順序數值越高,優先順序越高。

idle任務的優先順序為0。

多個任務可以共享一個任務優先順序。

如果在FreeRTOSConfig.h檔案中配置巨集定義configUSE_TIME_SLICING為1,或者沒有配置此巨集定義,時間片排程都是使能的。

使能時間片後,處於就緒態的多個相同優先順序任務將會以時間片切換的方式共享處理器。

如果硬體架構支援CLZ指令,可以使用該特性,使能配置如下:

  1. FreeRTOSConfig.hconfigUSE_PORT_OPTIMISED_TASK_SELECTION設定為1;
  2. 最大優先順序數目configMAX_PRIORITIES不能大於CPU位數。

空閒任務和空閒任務鉤子

空閒任務

空閒任務是啟動RTOS排程器時由核心自動建立的任務,其優先順序為0,確保系統中至少有一個任務在執行。

空閒任務可用來釋放RTOS分配給被刪除任務的記憶體。

空閒任務鉤子

空閒任務鉤子是一個函式,每一個空閒任務週期被呼叫一次。

空閒任務鉤子應該滿足一下條件:

  1. 不可以呼叫可能引起空閒任務阻塞的API函式;
  2. 不應該陷入死迴圈,需要留出部分時間用於系統處理系統資源回收。

建立空閒鉤子

FreeRTOSConfig.h標頭檔案中設定configUSE_IDLE_HOOK為1;

定義一個函式,名字和引數原型如下所示:

void vApplicationIdleHook( void ); // FreeRTOS 規定了函式的名字和引數

一般設定CPU進入低功耗模式都是使用空閒任務鉤子函式實現的。

建立任務

任務的建立有兩種:建立靜態記憶體任務和建立動態記憶體任務。

任務引數相關概念

任務入口函式:即是任務函式,是該任務需要跑的函式。

任務名稱:即是任務名,主要用於除錯。

任務堆疊大小:即是任務棧大小,單位是word。

任務入口函式引數:傳遞給任務入口函式的引數。在任務函式裡,通過形參獲得。

任務控制塊:主要用於核心管理任務,記錄任務資訊。

任務控制程式碼:用於區分不同的任務,用於找到該任務的任務控制塊。

建立靜態記憶體任務

xTaskCreateRestrictedStatic(),該函式不講解,因為需要MPU,想研究的同學可以參考:freertos官網API

配置靜態記憶體

建立靜態記憶體任務需要先實現以下內容:

  1. 需要在FreeRTOSConfig.h開啟configSUPPORT_STATIC_ALLOCATION巨集,開啟靜態記憶體。

  2. 開啟靜態記憶體的同時需要實現兩個函式:(使用靜態記憶體分配任務堆疊和任務控制塊記憶體)

    1. vApplicationGetIdleTaskMemory():空閒任務堆疊函式。
    2. vApplicationGetTimerTaskMemory():定時器任務堆疊函式。
  3. 注意靜態記憶體對齊。

實現空閒任務堆疊函式

實現該函式是為了給核心提供空閒任務關於空閒任務控制塊和空閒任務堆疊的相關資訊。

/* 空閒任務控制塊 */
static StaticTask_t Idle_Task_TCB;
/* 空閒任務任務堆疊 */
static StackType_t Idle_Task_Stack[configMINIMAL_STACK_SIZE];

/** @brief vApplicationGetIdleTaskMemory
  * @details 獲取空閒任務的任務堆疊和任務控制塊記憶體
  * @param 
  * @retval 
  * @author lizhuming
  */
void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer,
                                   StackType_t **ppxIdleTaskStackBuffer,
                                   uint32_t *pulIdleTaskStackSize)
{
    *ppxIdleTaskTCBBuffer = &Idle_Task_TCB; /* 任務控制塊記憶體 */
    *ppxIdleTaskStackBuffer = Idle_Task_Stack; /* 任務堆疊記憶體 */
    *pulIdleTaskStackSize = configMINIMAL_STACK_SIZE; /* 任務堆疊大小 */
}

實現定時器任務堆疊函式

實現該函式是為了給核心建立定時器任務時提供定時器任務控制塊和定時器任務堆疊的相關資訊。

/* 定時器任務控制塊 */
static StaticTask_t Timer_Task_TCB;
/* 定時器任務堆疊 */
static StackType_t Timer_Task_Stack[configTIMER_TASK_STACK_DEPTH];

/** @brief vApplicationGetTimerTaskMemory
  * @details 獲取定時器任務的任務堆疊和任務控制塊記憶體
  * @param 
  * @retval 
  * @author lizhuming
  */
void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer,
                                    StackType_t **ppxTimerTaskStackBuffer,
                                    uint32_t *pulTimerTaskStackSize)
{
    *ppxTimerTaskTCBBuffer = &Timer_Task_TCB;/* 任務控制塊記憶體 */
    *ppxTimerTaskStackBuffer = Timer_Task_Stack;/* 任務堆疊記憶體 */
    *pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;/* 任務堆疊大小 */

配置記憶體對齊

記憶體對齊的配置在portmacro.h裡面的portBYTE_ALIGNMENT巨集,按自己需求配置即可。

在任務堆疊初始化時會把棧頂指標糾正為記憶體對齊。參考下列程式碼:

pxTopOfStack = &( pxNewTCB->pxStack[ ulStackDepth - ( uint32_t ) 1 ] );
pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );

糾正後可以通過以下程式碼檢查是否正確的程式碼如下:

configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );

分配靜態記憶體

靜態記憶體分配是有編譯器決定的。

在freertos中,建立任務需要分配的記憶體主要是任務控制塊和任務堆疊。

/* 任務控制快 */
static StaticTask_t lzmStaticTestTaskTCB = {0};
/* 任務堆疊 */
static StackType_t lzmStaticTestTaskStack[256] = {0};

建立任務原型

建立任務函式原型:

TaskHandle_t xTaskCreateStatic( // 返回任務控制程式碼
                                TaskFunction_t pxTaskCode, // 任務入口函式
                                const char * const pcName, // 任務名稱
                                const uint32_t ulStackDepth, // 任務堆疊大小
                                void * const pvParameters, // 傳遞給任務入口函式的引數
                                UBaseType_t uxPriority, // 任務優先順序
                                StackType_t * const puxStackBuffer, // 任務堆疊
                                StaticTask_t * const pxTaskBuffer ) // 任務控制塊

建立任務

/* 建立靜態記憶體任務 */
lzmStaticTestTaskHandle = xTaskCreateStatic((TaskFunction_t) lzmStaticTestTask, // 任務入口函式
                                            (const char*) "lzm static test task", // 任務函式名
                                            (uint32_t   )256, // 任務堆疊大小
                                            (void*      )NULL, // 傳遞給任務入口函式的引數
                                            (UBaseType_t)5, // 任務優先及
                                            (StackType_t*  )lzmStaticTestTaskStack, // 任務堆疊地址
                                            (StaticTask_t* )&lzmStaticTestTaskTCB); // 任務控制塊地址

建立動態記憶體任務

配置動態記憶體

動態記憶體配置是在FreeRTOSConfig.h配置的,這些記憶體主要供給FreeRTOS動態記憶體分配函式使用。

#define configTOTAL_HEAP_SIZE	( ( size_t ) ( 32 * 1024 ) ) // 系統總堆大小

而freertos的動態記憶體管理是有檔案heap_x.c實現的,具體實現演算法,後面講到記憶體時會分析。

uint8_t ucHeap[ configTOTAL_HEAP_SIZE ]; // 系統總堆

任務控制程式碼

static TaskHandle_t lzmTestTaskHandle = NULL;

建立任務原型

建立任務函式原型:

BaseType_t xTaskCreate( // 返回任務控制程式碼
                        TaskFunction_t pxTaskCode, // 任務入口函式
                        const char * const pcName, // 任務名稱
                        const configSTACK_DEPTH_TYPE usStackDepth, // 任務堆疊大小
                        void * const pvParameters, // 傳遞給任務入口函式的引數
                        UBaseType_t uxPriority, // 任務優先順序
                        TaskHandle_t * const pxCreatedTask ) // 任務控制塊指標  

建立任務

/* 建立動態記憶體任務 */
xReturn = xTaskCreate((TaskFunction_t) lzmTestTask, // 任務入口函式
                      (const char*) "lzm test task", // 任務函式名
                      (uint16_t   )256, // 任務堆疊大小
                      (void*      )NULL, // 傳遞給任務入口函式的引數
                      (UBaseType_t)5, // 任務優先及
                      (TaskHandle_t* )&lzmTestTaskHandle); // 任務控制程式碼

刪除任務

配置刪除任務

在檔案FreeRTOSConfig.h中,必須定義巨集INCLUDE_vTaskDelete 為 1,刪除任務的API才會失效。

呼叫API刪除任務後,將會從就緒、阻塞、暫停和事件列表中移除該任務。

如果是動態記憶體建立任務,刪除任務後,其佔用的空間資源有空閒任務釋放,所以刪除任務後儘量保證空閒任務獲取一定的CPU時間。

如果是靜態記憶體建立任務,刪除任務後,需要自己處理釋放任務佔用的空間資源。

刪除任務原型

void vTaskDelete( TaskHandle_t xTaskToDelete ); // 引數為任務控制程式碼

注意:傳入的引數為任務控制程式碼,當出入的引數為NULL時,表示刪除呼叫者當前的任務。

實戰

原始碼:拉取 freertos_on_linux_task_01 資料夾

結果: