1.前言
FreeRTOS是小型多任務嵌入式操作系統,硬實時性。本章主要講述任務相關特性及調度相關的知識。
2. 任務的總體特點
- 任務的狀態
(1)任務有兩個狀態,運行態和非運行態
(2)任務由非運行態轉入運行態為切入,相反為切出
- 設置優先級
(1)最高優先級在FreeRTOSConfig.h 中 設 定 的 編 譯 時 配 置 常 量configMAX_PRIORITIES中進行設置。
注:FreeRTOS 本身並沒有限定這個常量的最大值,但這個值越大,則內核花銷的內存空間就越多。建議將此常量設為能夠用到的最小值(2)任意數量的任務可以共享同一個優先級也可以為每個任務唯一指定一個優先級
(3)低優先級號表示任務的優先級低
(4)有效的優先級號范圍從 0 到(configMAX_PRIORITES – 1)
(5)調度器保證總是在所有可運行的任務中選擇具有最高優先級的任務,並使其進入運行態
(6)如果被選中的優先級上具有不止一個任務,調度器會讓這些任務輪流執行(7)調度器總是選擇所有能夠進入運行態的任務中具有最高優先級的任務
- 改變任務優先級
可以通過vTaskPrioritySet() 來改變任務的優先級
- 任務時間片
(1)兩個任務被創建在同一個優先級上,並且一直是可運行的。所以每個任務都執行一個”時間片”,任務在時間片起始時刻進入運行態,在時間片結束時刻又退出運行態
(2)調度器需要在每個時間片結束時能夠調度自己, tick中斷可以完成此目的
(3)tick中斷頻率可以通過FreeRTOSConfig.h 中的編譯時配置常量configTICK_RATE_HZ 進行配置,如設為100HZ,則時間片長度為10ms
3.任務狀態機
3.1 任務狀態機
圖 完整的任務狀態機
- 阻塞態(blocked)
(1)如果一個任務正在等待某個事件,則稱這個任務處於”阻塞態(blocked)”。阻塞態是非運行態的一個子狀態。
(2)任務進入阻塞態一般等待以下兩種事件:定時事件和同步事件
(3)任務可以在進入阻塞態等待同步事件時指定一個等待超時時間,這樣可以有效地實現阻塞狀態下同時等待兩種類型的事件,
比如說,某個任務可以等待隊列中有數據到來,但最多只等10ms。如果10ms 內有數據到來,或是10ms 過去了還沒有數據到來,這兩種情況下該任務都將退出阻塞態。
- 掛起狀態(suspended)
(1)處於掛起狀態的任務對調度器而言是不可見的
(2)讓一個任務進入掛起狀態的唯一辦法就是調用vTaskSuspend() API 函數
(3)把一個掛起狀態的任務喚醒的唯一途徑就是調用vTaskResume() 或TaskResumeFromISR() API 函數
(4)大多數應用程序中都不會用到掛起狀態
- 就緒狀態(ready)
(1)如果任務處於非運行狀態,但既沒有阻塞也沒有掛起,則這個任務處於就緒(ready,准備或就緒)狀態
(2)處於就緒態的任務能夠被運行,但只是”准備(ready)”運行,而當前尚未運行
3.2.利用阻塞態實現延遲
1 void vTaskDelay( portTickType xTicksToDelay );
調用上面的函數的任務會進入阻塞態,xTicksToDelay表示delay的tick周期數,portTICK_RATE_MS代表每毫秒有多少個tick周期
4. 空閑任務
- 空閑任務的特點
(1)在調度器啟動的時候vTaskStartScheduler會自動創建一個空閑任務
(2)空閑任務具有最低的優先級0,保證不會影響其它更高優先級的任務進入就緒態
(3)運行在最低優先級可以保證一旦有更高優先級的任務進入就緒態,空閑任務就會立即切出運行態
(4)空間任務可以獲得的執行時間量,是系統處理能力裕量的一個度量指
- 空閑任務鈎子函數
(1)通過空閑任務鈎子函數(或稱回調,hook, or call-back),可以直接在空閑任務中添加應用程序相關的功能,空閑任務鈎子函數會被空閑任務每循環一次就自動調用一次
(2)空閑任務鈎子函數被用於:
---執行低優先級,后台或需要不停處理的功能代碼。
---測試系統處理裕量
---將處理器配置到低功耗模式——提供一種自動省電方法,使得在沒有任何應用功能需要處理的時候,系統自動進入省電模式。
- 空閑任務鈎子函數必須遵從以下規則
(1)絕不能阻塞或掛起。
空閑任務只會在其它任務都不運行時才會被執行(除非有應用任務共享空閑任務優先級)。
以任何方式阻塞空閑任務都可能導致沒有任務能夠進入運行態!
(2)如果應用程序用到了vTaskDelete() 函數,則空閑鈎子函數必須能夠盡快返回。因為在任務被刪除后,空閑任務負責回收內核資源。如果空閑任務一直運行在鈎子函數中,則無法進行回收工作。
5.任務的調度
- 優先級搶占式調度
每個任務都被賦予了一個優先級,這個優先級不能被內核本身改變(只能被任務修改)。
”搶占式”是指當任務進入就緒態或是優先級被改變時,如果處於運行態的任務優先級更高,則該任務總是搶占當前運行的任務
-
單調速率調度
單調速率調度(Rate Monotonic Scheduling, RMS)是一種常用的優先級分配技術。其根據任務周期性執行的速率來分配一個唯一的優先級。
具有最高周期執行頻率的任務賦予高最優先級;具有最低周期執行頻率的任務賦予最低優先級。這種優先級分配方式被證明了可以最大化整個應用程序的可調度性(schedulability),但是運行時間不定以及並非所有任務都具有周期性,會使得對這種方式的全面計算變得相當復雜
-
協作式調度
只可能在運行態任務進入阻塞態或是運行態任務顯式調用 taskYIELD()時,才會進行上下文切換。
任務永遠不會被搶占,而具有相同優先級的任務也不會自動共享處理器時間。協作式調度的這作工作方式雖然比較簡單,但可能會導致系統響應不夠快
- 混合調度
這需要在中斷服務例程中顯式地進行上下文切換,從而允許同步事件產生搶占行為,但時間事件卻不行。
這樣做的結果是得到了一個沒有時間片機制的搶占式系統。或許這正是所期望的,因為獲得了效率,並且這也是一種常用的調度器配置
6.任務調度舉例
- 兩個優先級相同的任務時間片執行圖如下:
圖 優先級相同的兩個任務的時間片執行序列
- 加入了tick中斷及tick中斷調度器調度下個任務圖示如下:
圖 對執行流程進行擴展以顯示tick中斷的執行
- 由於task2比task1優先級高,因此task1永遠得不到執行:
圖 Task2比Task1優先級高時的任務運行序列
- 用vTaskDelay()代替空循環后的執行流程,task1得到了執行,如下圖:
圖 用vTaskDelay()代替空循環后的執行流程
- continuous 1與continuous 2具有優先級1,periodic具有優先級2,periodic使用vTaskDelayUntil,調度圖如下(注意心跳中斷在t5時刻實際是時間片到時調度器執行):
- 開始任務1具有最高優先級,任務1結束前將任務2設為最高優先級,任務2在結束時將自身設為最低優先級,執行序列如下:
- idle在任務1進入阻塞態后,會執行對任務2的內存清除工作
- 三個任務(事件喚醒)的搶占執行過程
6. 一些重要的API
API | 含義 | 重要參數說明 | 返回值 |
void ATaskFunction( void *pvParameters ); | 任務的原型 | 參數必須是void * | 返回值必須是void |
portBASE_TYPE xTaskCreate( pdTASK_CODE pvTaskCode, const signed portCHAR * const pcName, unsigned portSHORT usStackDepth, void *pvParameters, unsigned portBASE_TYPE uxPriority, xTaskHandle *pxCreatedTask ); |
創建任務 |
表示任務的優先級,最好設置成實際任務需要的最小值,以避免浪費 |
|
void vTaskStartScheduler( void ) |
開始調度一個任務執行,第一次調度時會自動創建一個空閑任務 |
|
|
void vTaskDelete( TaskHandle_t xTaskToDelete ) |
刪除一個任務 |
要刪除任務的句柄 |
|
void vTaskDelay( const TickType_t xTicksToDelay ) |
讓當前任務進入阻塞態,並保持多少個tick |
用來指定任務在調用vTaskDelay()到切出阻塞態整個過程包含多少個tick |
|
void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, |
用於實現一個固定執行周期的需求求(當你需要讓你的任務以固定頻率周期性執行的時候) |
保存了任務上一次離開阻塞態(被喚醒)的時刻這個時刻被用作一個參考點來計算該任務下一次離開阻塞態的時刻。pxPreviousWakeTime 指向的變量值會在API 函數vTaskDelayUntil()調用過程中自動更新,應用程序除了該變量第一次初始化外(通過調用xTaskGetTickCount()初始化),通常都不要修改它的值
此參數命名時同樣是假定vTaskDelayUntil()用於實現某個任務以固定頻率周期性執行 —— 這個頻率就是由xTimeIncrement 指定的。xTimeIncrement 的單位是心跳周期, 可以使用常量 portTICK_RATE_MS 將毫秒轉換為心跳周期。 |
|
void vApplicationIdleHook( void ) |
空閑鈎子函數 | ||
void vTaskPrioritySet( xTaskHandle pxTask, |
改變任務優先級 |
被修改優先級的任務句柄(即目標任務)
目標任務將被設置到哪個優先級上。如果設置的值超過了最大可用優 |
|
unsigned portBASE_TYPE uxTaskPriorityGet( xTaskHandle pxTask ); |
用於查詢一個任務的優先級 |
被查詢任務的句柄(目標任務) |
返回值 被查詢任務的當前優先級 |
void vTaskDelete( xTaskHandle pxTaskToDelete ) |
刪除任務 |
被刪除任務的句柄(目標任務) 。任務可以通過傳入 NULL 值來刪除自己 |
|
taskYIELD |
調用此函數的任務轉入就緒態,同時另一個任務轉入運行態 |
7.參考文檔
[1] FreeRTOS中文實用教程