FreeRTOS實時操作系統(tǒng)多任務管理基礎知識
RTOS 系統(tǒng)的核心就是任務管理,F(xiàn)reeRTOS 也不例外,而且大多數(shù)學習 RTOS 系統(tǒng)的工程師或者學生主要就是為了使用 RTOS 的多任務處理功能,初步上手 RTOS 系統(tǒng)首先必須掌握的也是任務的創(chuàng)建、刪除、掛起和恢復等操作,由此可見任務管理的重要性。
什么是多任務系統(tǒng)?
回想一下我們以前在使用 51、AVR、STM32 單片機裸機(未使用系統(tǒng))的時候一般都是在main 函數(shù)里面用 while(1)做一個大循環(huán)來完成所有的處理,即應用程序是一個無限的循環(huán),循環(huán)中調用相應的函數(shù)完成所需的處理。有時候我們也需要中斷中完成一些處理。相對于多任務系統(tǒng)而言,這個就是單任務系統(tǒng),也稱作前后臺系統(tǒng),中斷服務函數(shù)作為前臺程序,大循環(huán)while(1)作為后臺程序
前后臺系統(tǒng)的實時性差,前后臺系統(tǒng)各個任務(應用程序)都是排隊等著輪流執(zhí)行,不管你這個程序現(xiàn)在有多緊急,沒輪到你就只能等著!相當于所有任務(應用程序)的優(yōu)先級都是一樣的。但是前后臺系統(tǒng)簡單啊,資源消耗也少?。≡谏晕⒋笠稽c的嵌入式應用中前后臺系統(tǒng)就明顯力不從心了,此時就需要多任務系統(tǒng)出馬了。
多任務系統(tǒng)會把一個大問題(應用)“分而治之”,把大問題劃分成很多個小問題,逐步的把小問題解決掉,大問題也就隨之解決了,這些小問題可以單獨的作為一個小任務來處理。這些小任務是并發(fā)處理的,注意,并不是說同一時刻一起執(zhí)行很多個任務,而是由于每個任務執(zhí)行的時間很短,導致看起來像是同一時刻執(zhí)行了很多個任務一樣。多個任務帶來了一個新的問題,究竟哪個任務先運行,哪個任務后運行呢?完成這個功能的東西在 RTOS 系統(tǒng)中叫做任務調度器。不同的系統(tǒng)其任務調度器的實現(xiàn)方法也不同,比如 FreeRTOS 是一個搶占式的實時多任務系統(tǒng),那么其任務調度器也是搶占式的。
高優(yōu)先級的任務可以打斷低優(yōu)先級任務的運行而取得 CPU 的使用權,這樣就保證了那些緊急任務的運行。這樣我們就可以為那些對實時性要求高的任務設置一個很高的優(yōu)先級,比如自動駕駛中的障礙物檢測任務等。高優(yōu)先級的任務執(zhí)行完成以后重新把 CPU 的使用權歸還給低優(yōu)先級的任務,這個就是搶占式多任務系統(tǒng)的基本原理。
FreeRTOS 任務與協(xié)程
FreeRTOS 中應用既可以使用任務,也可以使用協(xié)程(Co-Routine),或者兩者混合使用。但是任務和協(xié)程使用不同的API函數(shù),因此不能通過隊列(或信號量)將數(shù)據(jù)從任務發(fā)送給協(xié)程,反之亦然。協(xié)程是為那些資源很少的 MCU 準備的,其開銷很小,但是 FreeRTOS 官方已經(jīng)不打算再更新協(xié)程了,所以本教程只講解任務。
1.任務(Task) 的特性
在使用 RTOS 的時候一個實時應用可以作為一個獨立的任務。每個任務都有自己的運行環(huán)境,不依賴于系統(tǒng)中其他的任務或者 RTOS 調度器。任何一個時間點只能有一個任務運行,具體運行哪個任務是由 RTOS 調度器來決定的,RTOS 調度器因此就會重復的開啟、關閉每個任務。任務不需要了解 RTOS 調度器的具體行為,RTOS 調度器的職責是確保當一個任務開始執(zhí)行的時候其上下文環(huán)境(寄存器值,堆棧內容等)和任務上一次退出的時候相同。為了做到這一點,每個任務都必須有個堆棧,當任務切換的時候將上下文環(huán)境保存在堆棧中,這樣當任務再次執(zhí)行的時候就可以從堆棧中取出上下文環(huán)境,任務恢復運行。
任務特性: 簡單。沒有使用限制。支持搶占支持優(yōu)先級每個任務都擁有堆棧導致了 RAM 使用量增大。如果使用搶占的話的必須仔細的考慮重入的問題。
2.協(xié)程(Co-routine)的特性
協(xié)程是為那些資源很少的 MCU 而做的,但是隨著 MCU 的飛速發(fā)展,性能越來越強大,現(xiàn)
在協(xié)程幾乎很少用到了!但是 FreeRTOS 目前還沒有把協(xié)程移除的計劃,但是 FreeRTOS 是絕對
不會再更新和維護協(xié)程了,因此協(xié)程大家了解一下就行了。在概念上協(xié)程和任務是相似的,但
是有如下根本上的不同:
- 堆棧使用:所有的協(xié)程使用同一個堆棧(如果是任務的話每個任務都有自己的堆棧),這樣就比使用任務消耗更少的 RAM。
- 調度器和優(yōu)先級:協(xié)程使用合作式的調度器,但是可以在使用搶占式的調度器中使用協(xié)程。
- 宏實現(xiàn):協(xié)程是通過宏定義來實現(xiàn)的。
- 使用限制:為了降低對 RAM 的消耗做了很多的限制。
任務狀態(tài)
FreeRTOS 中的任務永遠處于下面幾個狀態(tài)中的某一個:
運行態(tài)
當一個任務正在運行時,那么就說這個任務處于運行態(tài),處于運行態(tài)的任務就是當前正在使用處理器的任務。如果使用的是單核處理器的話那么不管在任何時刻永遠都只有一個任務處于運行態(tài)。
就緒態(tài)
處于就緒態(tài)的任務是那些已經(jīng)準備就緒(這些任務沒有被阻塞或者掛起),可以運行的任務,但是處于就緒態(tài)的任務還沒有運行,因為有一個同優(yōu)先級或者更高優(yōu)先級的任務正在運行!
阻塞態(tài)
如果一個任務當前正在等待某個外部事件的話就說它處于阻塞態(tài),比如說如果某個任務調用了函數(shù) vTaskDelay()的話就會進入阻塞態(tài),直到延時周期完成。任務在等待隊列、信號量、事件組、通知或互斥信號量的時候也會進入阻塞態(tài)。任務進入阻塞態(tài)會有一個超時時間,當超過這個超時時間任務就會退出阻塞態(tài),即使所等待的事件還沒有來臨!
掛起態(tài)
像阻塞態(tài)一樣,任務進入掛起態(tài)以后也不能被調度器調用進入運行態(tài),但是進入掛起態(tài)的任務沒有超時時間。任務進入和退出掛起態(tài)通過調用函數(shù) vTaskSuspend()和 xTaskResume()。
任務優(yōu)先級
每 個 任 務 都 可 以 分 配 一 個 從 0~(configMAX_PRIORITIES-1) 的 優(yōu) 先 級 ,configMAX_PRIORITIES 在文件 FreeRTOSConfig.h 中有定義,前面我們講解 FreeRTOS 系統(tǒng)配置的時候已經(jīng)講過了。如果所使用的硬件平臺支持類似計算前導零這樣的指令(可以通過該指令選 擇 下 一 個 要 運 行 的 任 務 , Cortex-M 處 理 器 是 支 持 該 指 令 的 ) , 并 且 configUSE_PORT_OPTIMISED_TASK_SELECTION 也 設 置 為 了 1 , 那 么 宏configMAX_PRIORITIES 不能超過 32!也就是優(yōu)先級不能超過 32 級。其他情況下宏configMAX_PRIORITIES 可以為任意值,但是考慮到 RAM 的消耗,宏 configMAX_PRIORITIES最好設置為一個滿足應用的最小值。
優(yōu)先級數(shù)字越低表示任務的優(yōu)先級越低,0 的優(yōu)先級最低,configMAX_PRIORITIES-1 的優(yōu)先級最高。空閑任務的優(yōu)先級最低,為 0。
FreeRTOS 調度器確保處于就緒態(tài)或運行態(tài)的高優(yōu)先級的任務獲取處理器使用權,換句話說就是處于就緒態(tài)的最高優(yōu)先級的任務才會運行。當宏 configUSE_TIME_SLICING 定義為 1 的時候多個任務可以共用一個優(yōu)先級,數(shù)量不限。默認情況下宏configUSE_TIME_SLICING 在文件FreeRTOS.h 中已經(jīng)定義為 1。此時處于就緒態(tài)的優(yōu)先級相同的任務就會使用時間片輪轉調度器獲取運行時間。
任務實現(xiàn)
在使用 FreeRTOS 的過程中,我們要使用函數(shù) xTaskCreate()或 xTaskCreateStatic()來創(chuàng)建任務,這兩個函數(shù)的第一個參數(shù)pxTaskCode,就是這個任務的任務函數(shù)。什么是任務函數(shù)?任務函數(shù)就是完成本任務工作的函數(shù)。我這個任務要干嘛?要做什么?要完成什么樣的功能都是在這個任務函數(shù)中實現(xiàn)的。 比如我要做個任務,這個任務要點個流水燈,那么這個流水燈的程序就是任務函數(shù)中實現(xiàn)的。FreeRTOS 官方給出的任務函數(shù)模板如下:
void vATaskFunction(void *pvParameters) { for( ; ; ) { //--任務應用程序-- vTaskDelay(); /*此處不一定要用延時函數(shù),其他只要能讓 FreeRTOS 發(fā)生任務切換的 API 函數(shù)都可以, 比如請求信號量、隊列等,甚至直接調用任務調度器。只不過最常用的就是 FreeRTOS 的延時函數(shù)。*/ } /*不能從任務函數(shù)中返回或者退出,從任務函數(shù)中返回或退出的話就會調用 configASSERT(),前提是你定義了 configASSERT()。如果一定要從任務函數(shù)中退出的話那一定 要調用函數(shù) vTaskDelete(NULL)來刪除此任務。*/ //vTaskDelete(NULL); (5) }
任務控制塊
FreeRTOS 的每個任務都有一些屬性需要存儲,F(xiàn)reeRTOS 把這些屬性集合到一起用一個結構體來表示,這個結構體叫做任務控制塊:TCB_t,在使用函數(shù) xTaskCreate()創(chuàng)建任務的時候就會自動的給每個任務分配一個任務控制塊。在老版本的 FreeRTOS 中任務控制塊叫做 tskTCB,新版本重命名為 TCB_t,但是本質上還是 tskTCB,本教程后面提到任務控制塊的話均用 TCB_t表示,此結構體在文件 tasks.c 中有定義。 FreeRTOS 的任務控制塊中的成員變量相比 UCOSIII 要少很多,而且大多數(shù)與裁剪有關,當不使用某些功能的時候與其相關的變量就不參與編譯,任務控制塊大小就會進一步的減小。
typedef struct tskTaskControlBlock{ volatile StackType_t *pxTopOfStack; //任務堆棧棧頂 #if ( portUSING_MPU_WRAPPERS == 1 ) xMPU_SETTINGS xMPUSettings; //MPU 相關設置 #endif ListItem_t xStateListItem; //狀態(tài)列表項 ListItem_t xEventListItem; //事件列表項 UBaseType_t uxPriority; //任務優(yōu)先級 StackType_t *pxStack; //任務堆棧起始地址 char pcTaskName[ configMAX_TASK_NAME_LEN ];//任務名字 #if ( portSTACK_GROWTH > 0 ) StackType_t *pxEndOfStack; //任務堆棧棧底 #endif #if ( portCRITICAL_NESTING_IN_TCB == 1 ) UBaseType_t uxCriticalNesting; //臨界區(qū)嵌套深度 #endif #if ( configUSE_TRACE_FACILITY == 1 ) //trace 或到 debug 的時候用到 UBaseType_t uxTCBNumber; UBaseType_t uxTaskNumber; #endif #if ( configUSE_MUTEXES == 1 ) UBaseType_t uxBasePriority; //任務基礎優(yōu)先級,優(yōu)先級反轉的時候用到 UBaseType_t uxMutexesHeld; //任務獲取到的互斥信號量個數(shù) #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; //用來記錄任務運行總時間 #endif #if ( configUSE_NEWLIB_REENTRANT == 1 ) struct _reent xNewLib_reent; //定義一個 newlib 結構體變量 #endif #if( configUSE_TASK_NOTIFICATIONS == 1 ) //任務通知相關變量 volatile uint32_t ulNotifiedValue; //任務通知值 volatile uint8_t ucNotifyState; //任務通知狀態(tài) #endif #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) //用來標記任務是動態(tài)創(chuàng)建的還是靜態(tài)創(chuàng)建的,如果是靜態(tài)創(chuàng)建的此變量就為 pdTURE, //如果是動態(tài)創(chuàng)建的就為 pdFALSE uint8_t ucStaticallyAllocated; #endif #if( INCLUDE_xTaskAbortDelay == 1 ) uint8_t ucDelayAborted; #endif} tskTCB;//新版本的 FreeRTOS 任務控制塊重命名為 TCB_t,但是本質上還是 tskTCB,主要是為了兼容//舊版本的應用。typedef tskTCB TCB_t;typedef struct tskTaskControlBlock { volatile StackType_t *pxTopOfStack; //任務堆棧棧頂 #if ( portUSING_MPU_WRAPPERS == 1 ) xMPU_SETTINGS xMPUSettings; //MPU 相關設置 #endif ListItem_t xStateListItem; //狀態(tài)列表項 ListItem_t xEventListItem; //事件列表項 UBaseType_t uxPriority; //任務優(yōu)先級 StackType_t *pxStack; //任務堆棧起始地址 char pcTaskName[ configMAX_TASK_NAME_LEN ];//任務名字 #if ( portSTACK_GROWTH > 0 ) StackType_t *pxEndOfStack; //任務堆棧棧底 #endif #if ( portCRITICAL_NESTING_IN_TCB == 1 ) UBaseType_t uxCriticalNesting; //臨界區(qū)嵌套深度 #endif #if ( configUSE_TRACE_FACILITY == 1 ) //trace 或到 debug 的時候用到 UBaseType_t uxTCBNumber; UBaseType_t uxTaskNumber; #endif #if ( configUSE_MUTEXES == 1 ) UBaseType_t uxBasePriority; //任務基礎優(yōu)先級,優(yōu)先級反轉的時候用到 UBaseType_t uxMutexesHeld; //任務獲取到的互斥信號量個數(shù) #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; //用來記錄任務運行總時間 #endif #if ( configUSE_NEWLIB_REENTRANT == 1 ) struct _reent xNewLib_reent; //定義一個 newlib 結構體變量 #endif #if( configUSE_TASK_NOTIFICATIONS == 1 ) //任務通知相關變量 volatile uint32_t ulNotifiedValue; //任務通知值 volatile uint8_t ucNotifyState; //任務通知狀態(tài) #endif #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) //用來標記任務是動態(tài)創(chuàng)建的還是靜態(tài)創(chuàng)建的,如果是靜態(tài)創(chuàng)建的此變量就為 pdTURE, //如果是動態(tài)創(chuàng)建的就為 pdFALSE uint8_t ucStaticallyAllocated; #endif #if( INCLUDE_xTaskAbortDelay == 1 ) uint8_t ucDelayAborted; #endif } tskTCB; //新版本的 FreeRTOS 任務控制塊重命名為 TCB_t,但是本質上還是 tskTCB,主要是為了兼容 //舊版本的應用。 typedef tskTCB TCB_t;
任務堆棧
FreeRTOS 之所以能正確的恢復一個任務的運行就是因為有任務堆棧在保駕護航,任務調度器在進行任務切換的時候會將當前任務的現(xiàn)場(CPU 寄存器值等)保存在此任務的任務堆棧中,等到此任務下次運行的時候就會先用堆棧中保存的值來恢復現(xiàn)場,恢復現(xiàn)場以后任務就會接著從上次中斷的地方開始運行。
創(chuàng)建任務的時候需要給任務指定堆棧,如果使用的函數(shù) xTaskCreate()創(chuàng)建任務(動態(tài)方法)的話那么任務堆棧就會由函數(shù) xTaskCreate()自動創(chuàng)建,后面分析 xTaskCreate()的時候會講解。如果使用函數(shù) xTaskCreateStatic()創(chuàng)建任務(靜態(tài)方法)的話就需要程序員自行定義任務堆棧,然后堆棧首地址作為函數(shù)的參數(shù) puxStackBuffer 傳遞給函數(shù)。
任務堆棧的數(shù)據(jù)類型為 StackType_t,StackType_t 本質上是 uint32_t,在 portmacro.h 中有定義。所以 StackType_t 類型的變量為 4 個字節(jié),那么任務的實際堆棧大小就應該是我們所定義的 4 倍。
以上就是FreeRTOS實時操作系統(tǒng)多任務管理基礎知識的詳細內容,更多關于FreeRTOS實時操作系統(tǒng)多任務管理的資料請關注腳本之家其它相關文章!
相關文章
FreeRTOS實時操作系統(tǒng)的任務創(chuàng)建與任務切換
這篇文章主要為大家介紹了FreeRTOS實時操作系統(tǒng)的任務創(chuàng)建與任務切換,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步早日升職加薪2022-04-04FreeRTOS實時操作系統(tǒng)Cortex-M內核使用注意事項
這篇文章主要為大家介紹了FreeRTOS實時操作系統(tǒng)Cortex-M內核使用注意事項,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步早日升職加薪2022-04-04freertos實時操作系統(tǒng)空閑任務阻塞延時示例解析
這篇文章主要為大家介紹了freertos實時操作系統(tǒng)的空閑任務及阻塞延時示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-04-04