【freertos】003-任務基礎知識

前言

資源:

任務概念

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

線程:線程是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 文件夾

結果: