从零入门 FreeRTOS 操作系统之任务调度器
從零入門 FreeRTOS 操作系統之任務調度器
1 任務調度器的概念
FreeRTOS 中提供的任務調度器是基于優先級的全搶占式調度:在系統中除了中斷處理函數、調度器上鎖部分的代碼和禁止中斷的代碼是不可搶占的之外,系統的其他部分都是可以搶占的。
系統理論上可以支持無數個優先級(0 ~ N),優先級數值越小的任務優先級越低,0 為最低優先級,分配給空閑任務使用,一般不建議用戶來使用這個優先級。假如使能了 configUSE_PORT_OPTIMISED_TASK_SELECTION 這個宏(在 FreeRTOSConfig.h 文件中定義),一般強制限定最大可用優先級數目為32。在一些資源比較緊張的系統中,可以根據實際情況選擇只支持 8 個或 32 個優先級的系統配置。在系統中,當有比當前任務優先級更高的任務就緒時,當前任務將立刻被換出,高優先級任務搶占處理器運行。
一個操作系統如果只是具備了高優先級任務能夠“立即”獲得處理器并得到執行的特點,那么它仍然不算是實時操作系統。因為這個查找最高優先級任務的過程決定了調度時間是否具有確定性,例如一個包含 n 個就緒任務的系統中,如果僅僅從頭找到尾,那么這個時間將直接和 n 相關,而下一個就緒任務抉擇時間的長短將會極大的影響系統的實時性。
FreeRTOS 內核中采用兩種方法尋找最高優先級的任務,第一種是通用的方法,在就緒鏈表中查找從高優先級往低查找 uxTopPriority,因為在創建任務的時候已經將優先級進行排序,查找到的第一個 uxTopPriority 就是我們需要的任務,然后通過 uxTopPriority 獲取對應的任務控制塊。
第二種方法則是特殊方法,利用計算前導零指令 CLZ,直接在 uxTopReadyPriority 這個 32 位的變量中直接得出 uxTopPriority,這樣子就知道哪一個優先級任務能夠運行,這種調度算法比普通方法更快捷,但受限于平臺(在 STM32 中我們就使用這種方法)。
FreeRTOS 內核中也允許創建相同優先級的任務。相同優先級的任務采用時間片輪轉方式進行調度(也就是通常說的分時調度器),時間片輪轉調度僅在當前系統中無更高優先級就緒任務存在的情況下才有效。為了保證系統的實時性,系統盡最大可能地保證高優先級的任務得以運行。
任務調度的原則是一旦任務狀態發生了改變,并且當前運行的任務優先級小于優先級隊列組中任務最高優先級時,立刻進行任務切換(除非當前系統處于中斷處理程序中或禁止任務切換的狀態)。
2 任務的狀態
FreeRTOS 系統中的每一任務都有多種運行狀態。系統初始化完成后,創建的任務就可以在系統中競爭一定的資源,由內核進行調度。
任務狀態通常分為以下四種:
- 就緒(Ready):該任務在就緒列表中,就緒的任務已經具備執行的能力,只等待調度器進行調度,新創建的任務會被初始化為就緒態。
- 運行(Running):該狀態表明任務正在執行,此時它占用處理器,FreeRTOS 調度器選擇運行的永遠是處于最高優先級的就緒態任務,當任務被運行的一刻,它的任務狀態就變成了運行態。
- 阻塞(Blocked):如果任務當前正在等待某個時序或外部中斷,那么這個任務就處于阻塞狀態,該任務不在就緒列表中。包含任務被掛起、任務被延時、任務正在等待信號量、讀寫隊列或者等待讀寫事件等。
- 掛起態(Suspended):處于掛起態的任務對調度器而言是不可見的,讓一個任務進入掛起狀態的唯一辦法就是調用 vTaskSuspend() 函數;而把一個掛起狀態的任務恢復的唯一途徑就是調用 vTaskResume() 或 vTaskResumeFromISR() 函數。
掛起態與阻塞態的區別,當任務有較長的時間不允許運行的時候,可以掛起任務,這樣調度器就不會管這個任務的任何信息,直到調用恢復任務的 API 函數;而任務處于阻塞態的時候,系統還需要判斷阻塞態的任務是否超時,是否可以解除阻塞。
3 任務狀態遷移
我們知道 FreeRTOS 系統中的每一個任務都有多種運行狀態,下面來看一下狀態之間的轉換關系,任務遷移圖如下圖所示:
- ① 創建任務→就緒態(Ready):任務創建完成后進入就緒態,表明任務已準備就緒,隨時可以運行,只等待調度器進行調度。
- ② 就緒態→運行態(Running):發生任務切換時,就緒列表中最高優先級的任務被執行,從而進入運行態。
- ③ 運行態→就緒態:有更高優先級任務創建或者恢復后,會發生任務調度,此刻就緒列表中最高優先級任務變為運行態,那么原先運行的任務由運行態變為就緒態,依然在就緒列表中,等待最高優先級的任務運行完畢繼續運行原來的任務(此處可以看做是 CPU 使用權被更高優先級的任務搶占了)。
- ④ 運行態→阻塞態(Blocked):正在運行的任務發生阻塞(掛起、延時、讀信號量等待)時,該任務會從就緒列表中刪除,任務狀態由運行態變成阻塞態,然后發生任務切換,運行就緒列表中當前最高優先級任務。
- ⑤ 阻塞態→就緒態:阻塞的任務被恢復后(任務恢復、延時時間超時、讀信號量超時或讀到信號量等),此時被恢復的任務會被加入就緒列表,從而由阻塞態變成就緒態;如果此時被恢復任務的優先級高于正在運行任務的優先級,則會發生任務切換,將該任務將再次轉換任務狀態,由就緒態變成運行態。
- ⑥⑦⑧ 就緒態、阻塞態、運行態→掛起態(Suspended):任務可以通過調用 vTaskSuspend() 函數都可以將處于任何狀態的任務掛起,被掛起的任務得不到 CPU 的使用權,也不會參與調度,除非它從掛起態中解除。
- ⑨ 掛起態→就緒態:把一個掛起狀態的任務恢復的唯一途徑就是調用 vTaskResume() 或 vTaskResumeFromISR() 函數,如果此時被恢復任務的優先級高于正在運行任務的優先級,則會發生任務切換,將該任務將再次轉換任務狀態,由就緒態變成運行態。
4 任務設計要點
作為一個嵌入式開發人員,要對自己設計的嵌入式系統要了如指掌,任務的優先級信息,任務與中斷的處理,任務的運行時間、邏輯、狀態等都要知道,才能設計出好的系統,所以,在設計的時候需要根據需求制定框架。在設計之初就應該考慮下面幾點因素:任務運行的上下文環境、任務的執行時間合理設計。
FreeRTOS 中程序運行的上下文包括:
- 中斷服務函數
- 普通任務
- 空閑任務
(一)中斷服務函數
中斷服務函數是一種需要特別注意的上下文環境,它運行在非任務的執行環境下(一般為芯片的一種特殊運行模式(也被稱作特權模式))。在這個上下文環境中不能使用掛起當前任務的操作,不允許調用任何會阻塞運行的 API 函數接口。
另外需要注意的是,中斷服務程序最好保持精簡短小,快進快出,一般在中斷服務函數中只做標記事件的發生,然后通知任務,讓對應任務去執行相關處理,因為中斷服務函數的優先級高于任何優先級的任務,如果中斷處理時間過長,將會導致整個系統的任務無法正常運行。所以在設計的時候必須考慮中斷的頻率、中斷的處理時間等重要因素,以便配合對應中斷處理任務的工作。
(二)普通任務
任務看似沒有什么限制程序執行的因素,似乎所有的操作都可以執行。但是做為一個優先級明確的實時系統,如果一個任務中的程序出現了死循環操作(此處的死循環是指沒有阻塞機制的任務循環體),那么比這個任務優先級低的任務都將無法執行,當然也包括了空閑任務,因為死循環的時候,任務不會主動讓出 CPU,低優先級的任務是不可能得到 CPU 的使用權的,而高優先級的任務就可以搶占 CPU。這個情況在實時操作系統中是必須注意的一點,所以在任務中不允許出現死循環。
如果一個任務只有就緒態而無阻塞態,勢必會影響到其他低優先級任務的執行,所以在進行任務設計時,就應該保證任務在不活躍的時候,任務可以進入阻塞態以交出 CPU 使用權,這就需要我們明確知道什么情況下讓任務進入阻塞態,保證低優先級任務可以正常運行。在實際設計中,一般會將緊急的處理事件的任務優先級設置得高一些。
(三)空閑任務
空閑任務(idle 任務)是 FreeRTOS 系統中沒有其他工作進行時自動進入的系統任務。
因為處理器總是需要代碼來執行,所以至少要有一個任務處于運行態。FreeRTOS 為了保證這一點,當調用 vTaskStartScheduler() 時,調度器會自動創建一個空閑任務,空閑任務是一個非常短小的循環。
用戶可以通過空閑任務鉤子方式,在空閑任務上鉤入自己的功能函數。通常這個空閑任務鉤子能夠完成一些額外的特殊功能,例如系統運行狀態的指示,系統省電模式等。除了空閑任務鉤子,FreeRTOS 系統還把空閑任務用于一些其他的功能,比如當系統刪除一個任務或一個動態任務運行結束時,在執行刪除任務的時候,并不會釋放任務的內存空間,只會將任務添加到結束列表中,真正的系統資源回收工作在空閑任務完成,空閑任務是唯一一個不允許出現阻塞情況的任務,因為 FreeRTOS 需要保證系統永遠都有一個可運行的任務。
對于空閑任務鉤子上掛接的空閑鉤子函數,它應該滿足以下的條件:
- 永遠不會掛起空閑任務;
- 不應該陷入死循環,需要留出部分時間用于系統處理系統資源回收。
(四)任務的執行時間
任務的執行時間一般是指兩個方面,一是任務從開始到結束的時間,二是任務的周期。
在系統設計的時候這兩個時間都需要考慮。例如,事件 A 對應的服務任務 Ta,系統要求的實時響應指標是 10ms,而 Ta 的最大運行時間是 1ms,那么 10ms 就是任務 Ta 的周期了,1ms 則是任務的運行時間。簡單來說任務 Ta 在 10ms 內完成對事件 A 的響應即可。
如果此時系統中還存在著以 50ms 為周期的另一任務 Tb,它每次運行的最大時間長度是 100us。在這種情況下,即使把任務 Tb 的優先級抬到比 Ta 更高的位置,對系統的實時性指標也沒什么影響,因為即使在 Ta 的運行過程中,Tb 搶占了 Ta 的資源,等到 Tb 執行完畢,消耗的時間也只不過是100us,還是在事件 A 規定的響應時間內(10ms),Ta 能夠安全完成對事件A 的響應。但是假如系統中還存在任務 Tc,其運行時間為 20ms,假如將Tc 的優先級設置比 Ta 更高,那么在 Ta 運行的時候,突然間被 Tc 打斷,等到 Tc 執行完畢,那 Ta 已經錯過對事件 A(10ms) 的響應了,這是不允許的。
因此,在我們設計的時候,必須考慮任務的時間,一般來說處理時間更短的任務優先級應設置更高一些。
總結
以上是生活随笔為你收集整理的从零入门 FreeRTOS 操作系统之任务调度器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 怎么避免ChatGPT成为信息茧房?
- 下一篇: 怎么让ChatGPT更好地服务于社会?