FreeRTOS 任務(wù)管理
????FreeRTOS 的任務(wù)管理非常重要,了解任務(wù)管理的目的就是讓初學(xué)者從裸機(jī)的,單任務(wù)編程過渡到帶 OS 的,多任務(wù)編程上來。搞清楚了這一點(diǎn), 那么 FreeRTOS 學(xué)習(xí)就算入門了。
? ?
單任務(wù)系統(tǒng)?
????學(xué)習(xí)多任務(wù)系統(tǒng)之前,我們先來回顧下單任務(wù)系統(tǒng)的編程框架,即裸機(jī)時(shí)的編程框架。
????裸機(jī)編程主要是采用超級循環(huán)(super-loops)系統(tǒng),又稱前后臺系統(tǒng)。應(yīng)用程序是一個(gè)無限的循環(huán),循環(huán)中調(diào)用相應(yīng)的函數(shù)完成相應(yīng)的操作,循環(huán)這部分可以看做后臺行為;
????中斷服務(wù)程序處理異步事件,中斷服務(wù)這部分可以看做是前臺行為。后臺也可以叫做任務(wù)級,前臺也叫作中斷級
對于前后臺系統(tǒng)的編程思路主要有以下兩種方式:
1????查詢方式?
????對于一些簡單的應(yīng)用,處理器可以查詢數(shù)據(jù)或者消息是否就緒,就緒后進(jìn)行處理,然后再等待,如此循環(huán)下去。對于簡單的任務(wù),這種方式簡單易處理。但大多數(shù)情況下,需要處理多個(gè)接口數(shù)據(jù)或者消息,那就需要多次處理,如下面的流程圖所示:

????用查詢方式處理簡單的應(yīng)用,效果比較好,但是隨著工程的復(fù)雜,采用查詢方式實(shí)現(xiàn)的工程就變得很難維護(hù),同時(shí),由于無法定義查詢?nèi)蝿?wù)的優(yōu)先級,這種查詢方式會(huì)使得重要的接口消息得不到及時(shí)響應(yīng)。?
????比如程序一直在等待一個(gè)非緊急消息就緒,如果這個(gè)消息后面還有一個(gè)緊急的消息需要處理,那么就會(huì)使得緊急消息長時(shí)間得不到執(zhí)行。?
2 中斷方式?
對于查詢方式無法有效執(zhí)行緊急任務(wù)的情況,采用中斷方式就有效地解決了這個(gè)問題,下面是中斷方式簡單的流程圖:

采用中斷和查詢結(jié)合的方式可以解決大部分裸機(jī)應(yīng)用,但隨著工程的復(fù)雜,裸機(jī)方式的缺點(diǎn)就暴露出來了:
◆ 必須在中斷(ISR)內(nèi)處理時(shí)間關(guān)鍵運(yùn)算:?
????ISR 函數(shù)變得非常復(fù)雜,并且需要很長執(zhí)行時(shí)間。?
????ISR 嵌套可能產(chǎn)生不可預(yù)測的執(zhí)行時(shí)間和堆棧需求。
◆ 超級循環(huán)和 ISR 之間的數(shù)據(jù)交換是通過全局共享變量進(jìn)行的:
?????應(yīng)用程序的程序員必須確保數(shù)據(jù)一致性。
◆ 超級循環(huán)可以與系統(tǒng)計(jì)時(shí)器輕松同步,但:
?????如果系統(tǒng)需要多種不同的周期時(shí)間,則會(huì)很難實(shí)現(xiàn)。
????超過超級循環(huán)周期的耗時(shí)函數(shù)需要做拆分。?
?????增加軟件開銷,應(yīng)用程序難以理解。?
◆ 超級循環(huán)使得應(yīng)用程序變得非常復(fù)雜,因此難以擴(kuò)展:
????一個(gè)簡單的更改就可能產(chǎn)生不可預(yù)測的副作用,對這種副作用進(jìn)行分析非常耗時(shí)。?
????超級循環(huán)概念的這些缺點(diǎn)可以通過使用實(shí)時(shí)操作系統(tǒng) (RTOS) 來解決。
多任務(wù)系統(tǒng)?
針對這些情況,使用多任務(wù)系統(tǒng)就可以解決這些問題了。下面是一個(gè)多任務(wù)系統(tǒng)的流程圖:

????多任務(wù)系統(tǒng)或者說 RTOS 的實(shí)現(xiàn),重點(diǎn)就在這個(gè)調(diào)度器上,而調(diào)度器的作用就是使用相關(guān)的調(diào)度算法來決定當(dāng)前需要執(zhí)行的任務(wù)。
????如上圖所示的那樣,創(chuàng)建了任務(wù)并完成 OS 初始化后,就可以通過調(diào)度器來決定任務(wù) A,任務(wù) B 和任務(wù) C 的運(yùn)行,從而實(shí)現(xiàn)多任務(wù)系統(tǒng)。另外需要初學(xué)者注意的是,這里所說的多任務(wù)系統(tǒng)同一時(shí)刻只能有一個(gè)任務(wù)可以運(yùn)行,只是通過調(diào)度器的決策,看起來像所有任務(wù)同時(shí)運(yùn)行一樣。為 了更好的說明這個(gè)問題,再舉一個(gè)詳細(xì)的運(yùn)行例子,運(yùn)行條件如下:
◆ 使用搶占式調(diào)度器。
◆ 1 個(gè)空閑任務(wù),優(yōu)先級最低。?
◆ 2 個(gè)應(yīng)用任務(wù),一個(gè)高優(yōu)先級和一個(gè)低優(yōu)先級,優(yōu)先級都比空閑任務(wù)優(yōu)先級高。
◆ 中斷服務(wù)程序,含 USB 中斷,串口中斷和系統(tǒng)滴答定時(shí)器中斷。?
????下圖所示是任務(wù)的運(yùn)行過程,其中橫坐標(biāo)是任務(wù)優(yōu)先級由低到高排列,縱坐標(biāo)是運(yùn)行時(shí)間,時(shí)間刻度有小到大。

(1) 啟動(dòng) RTOS,首先執(zhí)行高優(yōu)先級任務(wù)(vTaskStartScheduler)。?
(2) 高優(yōu)先級任務(wù)等待事件標(biāo)志(xEventGroupWaitBits)被阻塞,低優(yōu)先級任務(wù)得到執(zhí)行。?
(3) 低優(yōu)先級任務(wù)執(zhí)行的過程中產(chǎn)生 USB 中斷,進(jìn)入 USB 中斷服務(wù)程序。?
(4) 退出 USB 中斷復(fù)位程序,回到低優(yōu)先級任務(wù)繼續(xù)執(zhí)行。?
(5) 低優(yōu)先級任務(wù)執(zhí)行過程中產(chǎn)生串口接收中斷,進(jìn)入串口接收中斷服務(wù)程序。?
(6) 退出串口接收中斷復(fù)位程序,并發(fā)送事件標(biāo)志設(shè)置消息(xEventGroupSetBitsFromISR), 被阻塞的高優(yōu)先級任務(wù)就會(huì)重新進(jìn)入就緒狀態(tài),這個(gè)時(shí)候高優(yōu)先級任務(wù)和低優(yōu)先級任務(wù)都在就緒態(tài),搶占式調(diào)度器就會(huì)讓高優(yōu)先級的任務(wù)先執(zhí)行,所以此時(shí)就會(huì)進(jìn)入高優(yōu)先級任務(wù)。?
(7) 高優(yōu)先級任務(wù)由于等待事件標(biāo)志(xEventGroupWaitBits)會(huì)再次被阻塞,低優(yōu)先級任務(wù)開始繼續(xù)執(zhí)行。?
(8) 低優(yōu)先級任務(wù)調(diào)用函數(shù) vTaskDelay,低優(yōu)先級任務(wù)被掛起,從而空閑任務(wù)得到執(zhí)行。 (9) 空閑任務(wù)執(zhí)行期間發(fā)生滴答定時(shí)器中斷,進(jìn)入滴答定時(shí)器中斷服務(wù)程序。?
(10) 退出滴答定時(shí)器中斷,由于低優(yōu)先級任務(wù)延時(shí)時(shí)間到,低優(yōu)先級任務(wù)繼續(xù)執(zhí)行。
(11) 低優(yōu)先級任務(wù)再次調(diào)用延遲函數(shù)vTaskDelay,低優(yōu)先級任務(wù)被掛起,從而切換到空閑任務(wù)。 空閑任務(wù)得到執(zhí)行。?
????FreeRTOS 就是一款支持多任務(wù)運(yùn)行的實(shí)時(shí)操作系統(tǒng),具有時(shí)間片,搶占式和合作式三種調(diào)度方法。 通過 FreeRTOS 實(shí)時(shí)操作系統(tǒng)可以將程序函數(shù)分成獨(dú)立的任務(wù),并為其提供合理的調(diào)度方式。
FreeRTOS 的任務(wù)棧設(shè)置
????不管是裸機(jī)編程還是 RTOS 編程,棧的分配大小都非常重要。
????局部變量,函數(shù)調(diào)用時(shí)的現(xiàn)場保護(hù)和返回地址,函數(shù)的形參,進(jìn)入中斷函數(shù)前和中斷嵌套等都需要棧空間,棧空間定義小了會(huì)造成系統(tǒng)崩潰。
????裸機(jī)的情況下,用戶可以在這里配置棧大?。?/p>
????不同于裸機(jī)編程,在 RTOS 下,每個(gè)任務(wù)都有自己的棧空間。
????對于 FreeRTOS 來說,任務(wù)??臻g是在任務(wù)創(chuàng)建的時(shí)候從 FreeRTOSConfig.h 文件中定義的 heap 空間中申請的?
????#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 17 * 1024 ) )
????具體每個(gè)任務(wù)的棧大小是在創(chuàng)建 FreeRTOS 的任務(wù)時(shí)進(jìn)行設(shè)置的:
FreeRTOS 的系統(tǒng)棧設(shè)置
裸機(jī)的情況下,凡是用到??臻g的地方 都是在這里配置的??臻g:

????在 RTOS 下,上圖中設(shè)置的棧大小有了一個(gè)新的名字叫系統(tǒng)??臻g,而任務(wù)棧是不使用這里的空間的。任務(wù)棧不使用這里的棧空間,哪里使用這里的??臻g呢?
????答案就在中斷函數(shù)和中斷嵌套。
????◆ 由于 Cortex-M3 和 M4 內(nèi)核具有雙堆棧指針,MSP 主堆棧指針和 PSP 進(jìn)程堆棧指針,或者叫 PSP 任務(wù)堆棧指針也是可以的。在 FreeRTOS 操作系統(tǒng)中,主堆棧指針 MSP 是給系統(tǒng)??臻g使用的,進(jìn)程堆棧指針 PSP 是給任務(wù)棧使用的。
????也就是說,在FreeRTOS 任務(wù)中,所有??臻g的使用都是通過 PSP 指針進(jìn)行指向的。一旦進(jìn)入了中斷函數(shù)以及可能發(fā)生的中斷嵌套都是用的 MSP 指針。這個(gè)知識點(diǎn)要記住它,當(dāng)前可以不知道這是為什么,但是一定要記住。
◆ 實(shí)際應(yīng)用中系統(tǒng)??臻g分配多大,主要是看可能發(fā)生的中斷嵌套層數(shù),下面我們就按照最壞執(zhí)行情況 進(jìn)行考慮,所有的寄存器都需要入棧,此時(shí)分為兩種情況
?????情況1:?64 字節(jié)?
?????對于 Cortex-M3 內(nèi)核和未使用 FPU(浮點(diǎn)運(yùn)算單元)功能的 Cortex-M4 內(nèi)核在發(fā)生中斷時(shí)需要將 16 個(gè)通用寄存器全部入棧,每個(gè)寄存器占用 4 個(gè)字節(jié),也就是 16*4 = 64 字節(jié)的空間。 可能發(fā)生幾次中斷嵌套就是要 64 乘以幾即可。當(dāng)然,這種是最壞執(zhí)行情況,也就是所有的寄存器都入棧。
????(注:任務(wù)執(zhí)行的過程中發(fā)生中斷的話,有 8 個(gè)寄存器是自動(dòng)入棧的,這個(gè)棧是任務(wù)棧,進(jìn)入中斷以后其余寄存器入棧以及發(fā)生中斷嵌套都是用的系統(tǒng)棧)
????情況2: 200 字節(jié)
????對于具有 FPU(浮點(diǎn)運(yùn)算單元)功能的 Cortex-M4 內(nèi)核,如果在任務(wù)中進(jìn)行了浮點(diǎn)運(yùn)算,那么在發(fā)生中斷的時(shí)候除了 16 個(gè)通用寄存器需要入棧,還有 34 個(gè)浮點(diǎn)寄存器也是要入棧的,也就是 (16+34)*4 = 200 字節(jié)的空間。當(dāng)然,這種是最壞執(zhí)行情況,也就是所有的寄存器都入棧。?
(注:任務(wù)執(zhí)行的過程中發(fā)送中斷的話,有 8 個(gè)通用寄存器和 18 個(gè)浮點(diǎn)寄存器是自動(dòng)入棧的, 這個(gè)棧是任務(wù)棧,進(jìn)入中斷以后其余通用寄存器和浮點(diǎn)寄存器入棧以及發(fā)生中斷嵌套都是用的系 統(tǒng)棧)
FreeRTOS 的任務(wù)狀態(tài)
? ?FreeRTOS 的運(yùn)行支持以下四種狀態(tài):
◆ Running—運(yùn)行態(tài)
當(dāng)任務(wù)處于實(shí)際運(yùn)行狀態(tài)被稱之為運(yùn)行態(tài),即 CPU 的使用權(quán)被這個(gè)任務(wù)占用。?
◆ Ready—就緒態(tài)?
處于就緒態(tài)的任務(wù)是指那些能夠運(yùn)行(沒有被阻塞和掛起),但是當(dāng)前沒有運(yùn)行的任務(wù),因?yàn)橥瑑?yōu)先級或更高優(yōu)先級的任務(wù)正在運(yùn)行。
◆ Blocked—阻塞態(tài)?
由于等待信號量,消息隊(duì)列,事件標(biāo)志組等而處于的狀態(tài)被稱之為阻塞態(tài),另外任務(wù)調(diào)用延遲函數(shù)也會(huì)處于阻塞態(tài)。
◆ Suspended—掛起態(tài)?
類似阻塞態(tài),通過調(diào)用函數(shù) vTaskSuspend()對指定任務(wù)進(jìn)行掛起,掛起后這個(gè)任務(wù)將不被執(zhí)行,只有調(diào)用函數(shù) xTaskResume()才可以將這個(gè)任務(wù)從掛起態(tài)恢復(fù)。

FreeRTOS 啟動(dòng)
使用如下函數(shù)即可啟動(dòng) FreeRTOS:?
◆ vTaskStartScheduler(); 關(guān)于這個(gè)函數(shù)的講解及其使用方法可以看 FreeRTOS 在線版手冊
????函數(shù) vTaskStartScheduler 用于啟動(dòng) FreeRTOS 調(diào)度器,即啟動(dòng) FreeRTOS 的多任務(wù)執(zhí)行。 使用這個(gè)函數(shù)要注意以下幾個(gè)問題:?
1. 空閑任務(wù)和可選的定時(shí)器任務(wù)是在調(diào)用這個(gè)函數(shù)后自動(dòng)創(chuàng)建的。?
2. 正常情況下這個(gè)函數(shù)是不會(huì)返回的,運(yùn)行到這里極有可能是用于定時(shí)器任務(wù)或者空閑任務(wù)的 heap 空間不足造成創(chuàng)建失敗,此時(shí)需要加大 FreeRTOSConfig.h 文件中定義的 heap 大?。?????#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 17 * 1024 ) )
FreeRTOS 的任務(wù)創(chuàng)建
使用如下函數(shù)可以實(shí)現(xiàn) FreeRTOS 的任務(wù)創(chuàng)建:?
◆ xTaskCreate() 關(guān)于這個(gè)函數(shù)的講解及其使用方法可以看 FreeRTOS 在線版手冊:
函數(shù) xTaskCreate 用于實(shí)現(xiàn) FreeRTOS 操作系統(tǒng)的任務(wù)創(chuàng)建,并且還可以自定義任務(wù)棧的大小。
◆ 第 1 個(gè)參數(shù)填創(chuàng)建任務(wù)的函數(shù)名。?
◆ 第 2 個(gè)參數(shù)是任務(wù)名,這個(gè)參數(shù)主要是用于調(diào)試目的,調(diào)試的時(shí)候方便看是哪個(gè)任務(wù)。?
◆ 第 3 個(gè)參數(shù)是任務(wù)棧大小,單位 word,也就是 4 字節(jié)。?
◆ 第 4 個(gè)參數(shù)是創(chuàng)建的任務(wù)函數(shù)的形參。?
◆ 第 5 個(gè)參數(shù)是任務(wù)句柄,用于區(qū)分不同的任務(wù)。
FreeRTOS 的任務(wù)刪除
使用如下函數(shù)可以實(shí)現(xiàn) FreeRTOS 的任務(wù)刪除:
◆ vTaskDelete() 關(guān)于這個(gè)函數(shù)的講解及其使用方法可以看 FreeRTOS 在線版手冊:
◆ 第 1 個(gè)參數(shù)填要?jiǎng)h除任務(wù)的句柄 使用這個(gè)函數(shù)要注意以下問題:
????1 使用此函數(shù)需要在 FreeRTOSConfig.h 配置文件中配置如下宏定義為?
????#define INCLUDE_vTaskDelete ????1
????2 如果用往此函數(shù)里面填的任務(wù) ID 是 NULL,即數(shù)值 0 的話,那么刪除的就是當(dāng)前正在執(zhí)行的任務(wù),此任務(wù)被刪除后,F(xiàn)reeRTOS 會(huì)切換到任務(wù)就緒列表里面下一個(gè)要執(zhí)行的最高優(yōu)先級任務(wù)。
? ? 3 在 FreeRTOS 中,創(chuàng)建任務(wù)所需的內(nèi)存需要在空閑任務(wù)中釋放,如果用戶在 FreeRTOS 中調(diào)用了這個(gè)函數(shù)的話,一定要讓空閑任務(wù)有執(zhí)行的機(jī)會(huì),否則這塊內(nèi)存是無法釋放的。
????另外,創(chuàng)建的這個(gè)任務(wù)在使用中申請了動(dòng)態(tài)內(nèi)存,這個(gè)內(nèi)存不會(huì)因?yàn)榇巳蝿?wù)被刪除而刪除,這一點(diǎn)要注意,一定要在刪除前將此內(nèi)存釋放。
FreeRTOS 的任務(wù)掛起
使用如下函數(shù)可以實(shí)現(xiàn) FreeRTOS 的任務(wù)掛起:
◆ xTaskSuspend()
函數(shù) vTaskSuspend 用于實(shí)現(xiàn) FreeRTOS 操作系統(tǒng)的任務(wù)掛起。
◆ 第 1 個(gè)參數(shù)填要掛起任務(wù)的句柄 使用這個(gè)函數(shù)要注意以下問題:
????1. 使用此函數(shù)需要在 FreeRTOSConfig.h 配置文件中配置如下宏定義為 1
????#define INCLUDE_vTaskSuspend ????1?
????2. 如果用往此函數(shù)里面填的任務(wù) ID 是 NULL,即數(shù)值 0 的話,那么掛起的就是當(dāng)前正在執(zhí)行的任務(wù), 此任務(wù)被掛起后,F(xiàn)reeRTOS 會(huì)切換到任務(wù)就緒列表里面下一個(gè)要執(zhí)行的高優(yōu)先級任務(wù)。
????3. 多次調(diào)用此函數(shù)的話,只需調(diào)用一次 vTaskResume 即可將任務(wù)從掛起態(tài)恢復(fù)。
FreeRTOS?的任務(wù)恢復(fù)
使用如下函數(shù)可以實(shí)現(xiàn) FreeRTOS 的任務(wù)恢復(fù):
◆ xTaskResume()
◆ 第 1 個(gè)參數(shù)填要恢復(fù)任務(wù)的句柄 使用這個(gè)函數(shù)要注意以下問題:?
????1. 使用此函數(shù)需要在 FreeRTOSConfig.h 配置文件中配置如下宏定義為 1?
????#define INCLUDE_vTaskSuspend 1?
????2. 多次調(diào)用函數(shù) vTaskSuspend 的話,只需調(diào)用一次 vTaskResume 即可將任務(wù)從掛起態(tài)恢復(fù)。?
????3. 此函數(shù)是用于任務(wù)代碼中調(diào)用的,故不可以在中斷服務(wù)程序中調(diào)用此函數(shù),中斷服務(wù)程序中使用的 xTaskResumeFromISR(),以后綴 FromISR 結(jié)尾。
FreeRTOS 的任務(wù)恢復(fù)(中斷方式)
◆ xTaskResumeFromISR()
函數(shù) vTaskResumeFromISR 用于實(shí)現(xiàn) FreeRTOS 操作系統(tǒng)的任務(wù)恢復(fù)。?
◆ 第 1 個(gè)參數(shù)填要恢復(fù)任務(wù)的句柄 使用這個(gè)函數(shù)要注意以下問題:
????1.使用此函數(shù)需要在 FreeRTOSConfig.h 配置文件中配置如下宏定義為 1?
????#define INCLUDE_xResumeFromISR ????1?
????2. 多次調(diào)用函數(shù) vTaskSuspend 的話,只需調(diào)用一次 vTaskResumeFromISR 即可將任務(wù)從掛起態(tài)恢復(fù)。?
????3. 如果用戶打算采用這個(gè)函數(shù)實(shí)現(xiàn)中斷與任務(wù)的同步,要注意一種情況,如果此函數(shù)的調(diào)用優(yōu)先于函數(shù) vTaskSuspend 被調(diào)用,那么此次同步會(huì)丟失,這種情況下建議使用信號量來實(shí)現(xiàn)同步。?
????4. 此函數(shù)是用于中斷服務(wù)程序中調(diào)用的,故不可以在任務(wù)中使用此函數(shù),任務(wù)中使用的是 vTaskResume。
FreeRTOS 的空閑任務(wù)
幾乎所有的小型 RTOS 中都會(huì)有一個(gè)空閑任務(wù),空閑任務(wù)屬于系統(tǒng)任務(wù),是必須要執(zhí)行的,用戶程 序不能將其關(guān)閉。不光小型系統(tǒng)中有空閑任務(wù),大型的系統(tǒng)里面也有的,比如 WIN7。
空閑任務(wù)主要有以下幾個(gè)作用:?
????◆ 用戶不能讓系統(tǒng)一直在執(zhí)行各個(gè)應(yīng)用任務(wù),這樣的話系統(tǒng)利用率就是 100%,系統(tǒng)就會(huì)一直超負(fù)荷運(yùn)行,所以空閑任務(wù)很有必要。
????◆ 為了更好的實(shí)現(xiàn)低功耗,空閑任務(wù)也很有必要,用戶可以在空閑任務(wù)中實(shí)現(xiàn)睡眠,停機(jī)等低功耗措施。
?