RTOS系統的核心就是任務管理,FreeRTOS 也不例外,而且大多數學習RTOS 系統的工程師或者學生主要就是為了使用RTOS 的多任務處理功能,初步上手RTOS 系統首先必須掌握的也是任務的創建、刪除、掛起和恢復等操作,由此可見任務管理的重要性。
以前在使用51、AVR、STM32單片機裸機(未使用系統)的時候一般都是在main 函數裡面用while(1)做一個大循環來完成所有的處理,即應用程序是一個無限的循環,循環中調用相應的函數完成所需的處理。有時候我們也需要中斷中完成一些處理。,即為單任務系統,也稱作前後台系統,中斷服務函數作為前台程序,大循環while(1)作為後台程序。前後台系統的實時性差,前後台系統各個任務(應用程序)都是排隊等著輪流執行,不管你這個程序現在有多緊急,沒輪到你就只能等著!相當於所有任務(應用程序)的優先級都是一樣的。但是前後台系統簡單啊,資源消耗也少!
▼ 單任務系統
在稍微大一點的嵌入式應用中前後台系統就明顯力不從心了,此時就需要多任務系統出馬了。多任務系統會把一個大問題(應用)“分而治之”,把大問題劃分成很多個小問題,逐步的把小問題解決掉,大問題也就隨之解決了,這些小問題可以單獨的作為一個小任務來處理。這些小任務是並發處理的,注意,並不是說同一時刻一起執行很多個任務,而是由於每個任務執行的時間很短,導致看起來像是同一時刻執行了很多個任務一樣。 多個任務帶來了一個新的問題, 究竟哪個任務先運行,哪個任務後運行呢?完成這個功能的東西在RTOS 系統中叫做任務調度器。 不同的系統其任務調度器的實現方法也不同,比如FreeRTOS是一個搶占式的實時多任務系統,那麼其任務調度器也是搶占式的,如圖:
▼ 多任務系統
圖中高優先級的任務可以打斷低優先級任務的運行而取得CPU 的使用權,這樣就保證了那些緊急任務的運行。這樣我們就可以為那些對實時性要求高的任務設置一個很高的優先級,比如自動駕駛中的障礙物檢測任務等。高優先級的任務執行完成以後重新把CPU 的使用權歸還給低優先級的任務,這個就是搶占式多任務系統的基本原理。
FreeRTOS 系統中的每一任務都有多種運行狀態。系統初始化完成後,創建的任務就可以在系統中競爭一定的資源,由內核進行調度。
就緒(Ready)
該任務在就緒列表中,就緒的任務已經具備執行的能力,只等待調度器進行調度,新創建的任務會初始化為就緒態。
運行(Running)
該狀態表明任務正在執行,此時它佔用處理器,FreeRTOS 調度器選擇運行的永遠是處於最高優先級的就緒態任務,當任務被運行的一刻,它的任務狀態就變成了運行態。
阻塞(Blocked)
如果任務當前正在等待某個時序或外部中斷,我們就說這個任務處於阻塞狀態,該任務不在就緒列表中。包含任務被掛起、任務被延時、任務正在等待信號量、讀寫隊列或者等待讀寫事件等。
掛起態(Suspended)
處於掛起態的任務對調度器而言是不可見的,讓一個任務進入掛起狀態的唯一辦法就是調用vTaskSuspend()函數;而把一個掛起狀態的任務恢復的唯一途徑就是調用vTaskResume() 或vTaskResumeFromISR()函數,我們可以這麼理解掛起態與阻塞態的區別,當任務有較長的時間不允許運行的時候,我們可以掛起任務,這樣子調度器就不會管這個任務的任何信息,直到我們調用恢復任務的API函數;而任務處於阻塞態的時候,系統還需要判斷阻塞態的任務是否超時,是否可以解除阻塞。
FreeRTOS 系統中的每一個任務都有多種運行狀態,他們之間的轉換關係是怎麼樣的呢?從運行態任務變成阻塞態,或者從阻塞態變成就緒態,這些任務狀態是如何進行遷移?
▼ FreeRTOS系統 任務狀態
創建任務→就緒態(Ready):任務創建完成後進入就緒態,表明任務已準備就緒,隨時可以運行,只等待調度器進行調度。
就緒態→運行態(Running):發生任務切換時,就緒列表中最高優先級的任務被執行,從而進入運行態。
運行態→就緒態:有更高優先級任務創建或者恢復後,會發生任務調度, 此刻就緒列表中最高優先級任務變為運行態,那麼原先運行的任務由運行態變為就緒態, 依然在就緒列表中,等待最高優先級的任務運行完畢繼續運行原來的任務(此處可以看做是CPU 使用權被更高優先級的任務搶占了)。
運行態→阻塞態(Blocked):正在運行的任務發生阻塞(掛起、延時、 讀信號量等待)時,該任務會從就緒列表中刪除,任務狀態由運行態變成阻塞態,然後發生任務切換,運行就緒列表中當前最高優先級任務。
阻塞態→就緒態:阻塞的任務被恢復後(任務恢復、延時時間超時、讀信號量超時或讀到信號量等),此時被恢復的任務會被加入就緒列表,從而由阻塞態變成就緒態;如果此時被恢復任務的優先級高於正在運行任務的優先級,則會發生任務切換, 將該任務將再次轉換任務狀態,由就緒態變成運行態。
就緒態、阻塞態、運行態→掛起態(Suspended):任務可以通過調用vTaskSuspend() API 函數都可以將處於任何狀態的任務掛起,被掛起的任務得不到CPU 的使用權,也不會參與調度,除非它從掛起態中解除。
掛起態→就緒態:把一個掛起狀態的任務恢復的唯一途徑就是調用vTaskResume() 或vTaskResumeFromISR() API 函數,如果此時被恢復任務的優先級高於正在運行任務的優先級,則會發生任務切換,將該任務將再次轉換任務狀態,由就緒態變成運行態。
動態創建 xTaskCreate()
使用動態的方式(系統分配堆棧空間)創建一個任務
需要定義configSUPPORT_DYNAMIC_ALLOCATION為1。
返回值
pdPASS:任務創建成功
errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY:任務創建失敗,因為堆內存不足
靜態創建 xTaskCreateStatic()
使用靜態的方式(用戶分配堆棧空間)創建一個任務
需要定義configSUPPORT_STATIC_ALLOCATION為1。
返回值
NULL:任務創建失敗。puxStackBuffer或者pxTaskBuffer為NULL的時候會導致該錯誤發生。
其他值:任務創建成功,返回任務的句柄。
任務刪除函數 vTaskDelete()
用於刪除一個任務。當一個任務刪除另外一個任務時,形參為要刪除任務創建時返回的任務句柄,如果是刪除自身, 則形參為NULL。刪除的任務將從所有就緒,阻塞,掛起和事件列表中刪除。
刪除一個用xTaskCreate()或者xTaskCreateStatic()創建的任務,被刪除了的任務不再存在。如果xTaskCreate()創建的(即動態方法),則堆棧和控制塊會在空閒任務中被釋放。使用vTaskDelete()刪除任務後必須給空閒任務一定的運行時間。而用戶分配的任務內存需要用戶釋放,比如某任務調用了pvPortMalloc()分配了500字節內存,則此任務刪除後,用戶必須調用vPortFree()將這500個字節釋放,否則會造成內存洩漏。
要想使用該函數必須在FreeRTOSConfig.h 中把INCLUDE_vTaskDelete 定義為1
任務掛起函數 vTaskSuspend()
掛起指定任務。被掛起的任務絕不會得到CPU的使用權,不管該任務具有什麼優先級。任務可以通過調用vTaskSuspend()函數都可以將處於任何狀態的任務掛起,被掛起的任務得不到CPU 的使用權,也不會參與調度,它相對於調度器而言是不可見的,除非它從掛起態中解除。
vTaskSuspendAll()
這個函數就是比較有意思的,將所有的任務都掛起,其實源碼很簡單,也很有意思, 不管三七二十一將調度器鎖定,並且這個函數是可以進行嵌套的,說白了掛起所有任務就是掛起任務調度器。調度器被掛起後則不能進行上下文切換,但是中斷還是使能的。當調度器被掛起的時候,如果有中斷需要進行上下文切換, 那麼這個任務將會被掛起,在調度器恢復之後才執行切換任務。
任務延時函數 vTaskDelay()
vTaskDelay()在我們任務中用得非常之多,每個任務都必須是死循環,並且是必須要有阻塞的情況,否則低優先級的任務就無法被運行了。
要想使用FreeRTOS 中的vTaskDelay() 函數必須在FreeRTOSConfig.h 中把INCLUDE_vTaskDelay 定義為1
vTaskDelayUntil()
在FreeRTOS 中,除了相對延時函數,還有絕對延時函數vTaskDelayUntil(),這個絕對延時常用於較精確的周期運行任務,比如我有一個任務,希望它以固定頻率定期執行, 而不受外部的影響,任務從上一次運行開始到下一次運行開始的時間間隔是絕對的,而不是相對的。