FreeRTOS學習筆記——任務基礎知識
1、什麼是多工系統?
1.1 前後臺系統
前後臺系統如圖所示:
圖1 前後臺系統
前後臺系統實時性差,所有的任務(應用程式)都是排隊輪流執行的。不管任務多緊急,沒輪到的時候只能等著。
1.2 多工系統
多工系統是把一個大問題分成多個小問題,逐步把小問題解決了,大問題也就隨著解決了,這些小問題可以單獨的作為一個小任務來處理。這些小任務是併發處理的,但不是同一時刻一起執行多個任務,而是每個任務執行的時間都很短,導致看起來就像同一時刻執行了很多個任務一樣。究竟那個任務先執行,那個任務後執行,完成這個功能的東西在FreeRTOS中叫做任務排程器。FreeRTOS是一個搶佔式的實時多工系統,那麼其任務排程器也是搶佔式的,執行過程如圖所示:
圖2 搶佔式多工系統
2、FreeRTOS
2.1 任務的特性
在使用RTOS的時候一個實時應用可以作為一個獨立的任務,每個任務都有自己的執行環境,不依賴其他任務或者RTOS排程器。任何一個時間點都只能有一個任務執行,具體執行哪一個任務由任務排程器來決定,RTOS的排程器因此就會重複的開啟、關閉每個任務。RTOS排程器的職責是確保當一個任務開始執行的時候其上下文環境(暫存器值,堆疊內容等)和任務上一次退出的時候保持一致。為了做到這一點,每個任務都必須有個堆疊,當任務切換的時候將上下文環境儲存在堆疊中,這樣當任務再次執行的時候就可以從堆疊中取出上下文環境,任務恢復執行。
任務特性:
- 簡單
- 沒有使用限制
- 支援搶佔
- 支援優先順序
- 每個任務都擁有堆疊導致了RAM使用量增大
- 如果使用了搶佔的話必須考慮重入的問題
3、任務的狀態
FreeRTOS中的任務永遠處於下面幾個狀態中的某一個:
- 執行態
當一個任務正在執行時,那麼就說這個任務處於執行態,處於執行態的任務就是當前正在使用處理器的任務。如果使用的是單核處理器的話那麼不管在任何時刻永遠都只有一個任務處於執行態。
- 就緒態
處於就緒態的任務是那些已經準備就緒(這些任務沒有被阻塞或者掛起),可以執行的任務,但是處於就緒態的任務還沒有執行,因為有一個同優先順序或者更高優先順序的任務正在執行!
- 阻塞態
如果一個任務當前正在等待某個外部事件的話就說它處於阻塞態,比如說如果某個任務呼叫了函式vTaskDelay()的話就會進入阻塞態,直到延時週期完成。任務在等待佇列、訊號量、事件組、通知或互斥訊號量的時候也會進入阻塞態。任務進入阻塞態會有一個超時時間,當超過這個超時時間任務就會退出阻塞態,即使所等待的事件還沒有來臨!
- 掛起態
像阻塞態一樣,任務進入掛起態以後也不能被排程器呼叫進入執行態,但是進入掛起態的任務沒有超時時間。任務進入和退出掛起態通過呼叫函式vTaskSuspend()和xTaskResume()。
圖3 任務狀態間的切換
4、任務優先順序
每個任務可以分配一個從0~(configMAX_PRIORITIES-1)的優先順序,如果所使用的平臺支援類似計算前導零這樣的指令(可以通過該指令選擇下一個要執行的任務,Cortex-M處理器支援該指令),並且巨集configUSE_PORT_OPTIMISED_TASK_SELECTION也設定為1,那麼巨集configMAX_PRIORITIES不能超過32,!其他情況下configMAX_PRIORITIES可以設定為任意值,但是考慮到RAM的消耗,巨集configMAX_PRIORITIES最好設定為一個滿足應用的最小值。
優先順序數字越小表示任務的優先順序越低,0的優先順序最低,configMAX_PRIORITIES-1的優先順序最高。空閒任務的優先順序最低為,0。
FreeRTOS排程器確保處於就緒態或者執行態的高優先順序的任務獲取處理器的使用權,換句話說就是出於就緒態的最高優先順序的任務才會執行。當巨集configUSE_TIME_SLICING定義在檔案FreeRTOS.h中已經定義為1.此時出於就緒態的優先順序相同的任務就會使用時間片輪轉排程器獲取執行時間。
5、任務實現
(1)、任務函式本質也是函式,所以肯定有任務名什麼的,不過這裡我們要注意:任務函式的返回型別一定要為void型別,也就是無返回值,而且任務的引數也是void指標型別的!任務函式名可以根據實際情況定義。
(2)、任務的具體執行過程是一個大迴圈,for(;;)就代表一個迴圈,作用和 while(1)一樣。
(3)、迴圈裡面就是真正的任務程式碼了,此任務具體要乾的活就在這裡實現!
(4)、FrecRTOS的延時函式,此處不一定要用延時函式,其他只要能讓 FrecRTOS 發生任務切換的API函式都可以,比如請求訊號量、佇列等,甚至直接呼叫任務排程器。只不過最常用的就是 FrecRTOS的延時函式。
(5)、任務函式一般不允許跳出迴圈,如果一定要跳出迴圈的話在跳出迴圈以後一定要呼叫函式vTaskDelete(NULL)刪除此任務!
FrccRTOS的任務函式和UCOS的任務函式模式基本相同的,不止 FrecRTOS,其他RTOS的任務函式基本也是這種方式的。
6、任務控制塊
FreeRTOS的每個任務都有一些屬性需要儲存,FrecRTOS把這些屬性集合到一起用一個結構體來表示,這個結構體叫做任務控制塊:TCB_t,在使用函式xTaskCreate()建立任務的時候就會自動的給每個任務分配一個任務控制塊。在老版本的 FreeRTOS中任務控制塊叫做tskTCB,新版本重新命名為TCB_t,但是本質上還是 tskTCB,本教程後面提到任務控制塊的話均用TCB_t表示,此結構體在檔案 tasks.c中有定義,如下:
typedef struct tskTaskControlBlock /* The old naming convention is used to prevent breaking kernel aware debuggers. */ { volatile StackType_t * pxTopOfStack; /*< Points to the location of the last item placed on the tasks stack. THIS MUST BE THE FIRST MEMBER OF THE TCB STRUCT. */ #if ( portUSING_MPU_WRAPPERS == 1 ) xMPU_SETTINGS xMPUSettings; /*< The MPU settings are defined as part of the port layer. THIS MUST BE THE SECOND MEMBER OF THE TCB STRUCT. */ #endif ListItem_t xStateListItem; /*< The list that the state list item of a task is reference from denotes the state of that task (Ready, Blocked, Suspended ). */ ListItem_t xEventListItem; /*< Used to reference a task from an event list. */ UBaseType_t uxPriority; /*< The priority of the task. 0 is the lowest priority. */ StackType_t * pxStack; /*< Points to the start of the stack. */ char pcTaskName[ configMAX_TASK_NAME_LEN ]; /*< Descriptive name given to the task when created. Facilitates debugging only. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */ #if ( ( portSTACK_GROWTH > 0 ) || ( configRECORD_STACK_HIGH_ADDRESS == 1 ) ) StackType_t * pxEndOfStack; /*< Points to the highest valid address for the stack. */ #endif #if ( portCRITICAL_NESTING_IN_TCB == 1 ) UBaseType_t uxCriticalNesting; /*< Holds the critical section nesting depth for ports that do not maintain their own count in the port layer. */ #endif #if ( configUSE_TRACE_FACILITY == 1 ) UBaseType_t uxTCBNumber; /*< Stores a number that increments each time a TCB is created. It allows debuggers to determine when a task has been deleted and then recreated. */ UBaseType_t uxTaskNumber; /*< Stores a number specifically for use by third party trace code. */ #endif #if ( configUSE_MUTEXES == 1 ) UBaseType_t uxBasePriority; /*< The priority last assigned to the task - used by the priority inheritance mechanism. */ UBaseType_t uxMutexesHeld; #endif #if ( configUSE_APPLICATION_TASK_TAG == 1 ) TaskHookFunction_t pxTaskTag; #endif #if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 ) void * pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ]; #endif #if ( configGENERATE_RUN_TIME_STATS == 1 ) uint32_t ulRunTimeCounter; /*< Stores the amount of time the task has spent in the Running state. */ #endif #if ( configUSE_NEWLIB_REENTRANT == 1 ) /* Allocate a Newlib reent structure that is specific to this task. * Note Newlib support has been included by popular demand, but is not * used by the FreeRTOS maintainers themselves. FreeRTOS is not * responsible for resulting newlib operation. User must be familiar with * newlib and must provide system-wide implementations of the necessary * stubs. Be warned that (at the time of writing) the current newlib design * implements a system-wide malloc() that must be provided with locks. * * See the third party link http://www.nadler.com/embedded/newlibAndFreeRTOS.html * for additional information. */ struct _reent xNewLib_reent; #endif #if ( configUSE_TASK_NOTIFICATIONS == 1 ) volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ]; volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ]; #endif /* See the comments in FreeRTOS.h with the definition of * tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE. */ #if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e731 !e9029 Macro has been consolidated for readability reasons. */ uint8_t ucStaticallyAllocated; /*< Set to pdTRUE if the task is a statically allocated to ensure no attempt is made to free the memory. */ #endif #if ( INCLUDE_xTaskAbortDelay == 1 ) uint8_t ucDelayAborted; #endif #if ( configUSE_POSIX_ERRNO == 1 ) int iTaskErrno; #endif } tskTCB;
7、任務堆疊
FrecRTOS之所以能正確的恢復一個任務的執行就是因為有任務堆疊在保駕護航,任務排程器在進行任務切換的時候會將當前任務的現場(CPU暫存器值等)儲存在此任務的任務堆疊中,等到此任務下次執行的時候就會先用堆疊中儲存的值來恢復現場,恢復現場以後任務就會接著從上次中斷的地方開始執行。
建立任務的時候需要給任務指定堆疊,如果使用的函式 xTaskCreate()建立任務(動態方法)的話那麼任務堆疊就會由函式xTaskCreate()自動建立,後面分析xTaskCreate()的時候會講解。如果使用函式 xTaskCrcatcStatic()建立任務(靜態方法)的話就需要程式設計師自行定義任務堆疊,然後堆疊首地址作為函式的引數puxStackBuffer 傳遞給函式,如下:
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode, const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */ const uint32_t ulStackDepth, void * const pvParameters, UBaseType_t uxPriority, StackType_t * const puxStackBuffer, StaticTask_t * const pxTaskBuffer )
(1)、任務堆疊,需要使用者定義,然後將堆疊首地址傳遞給這個引數。堆疊大小:
我們不管是使用函式xTaskCrcatc()還是xTaskCrcatcStatic()建立任務都需要指定任務堆疊大小。任務堆疊的資料型別為StackType_t,StackType_t本質上是uint32_t,在portmacro.h中有定義,如下:
#define portSTACK_TYPE uint32_t #define portBASE_TYPE long typedef portSTACK_TYPE StackType_t;
typedef long BaseType_t;
typedef unsigned long UBaseType_t;
可以看出 StackType_t型別的變數為4個位元組,那麼任務的實際堆疊大小就應該是我們所定義的4倍
參考正點原子:《STM32F429FreeRTOS開發手冊》