FreeRTOS --(7)任務管理之入門篇

目錄

1、任務狀態

2、任務創建

3、任務優先級

4、任務阻塞

5、任務掛起

6、空閒任務

5、任務調度

5.1、搶佔式調度

5.2、協作式調度


 

任務管理是操作系統中重中之重,不管什麼 OS ,任務的調度管理都是核心,FreeRTOS 也是一樣;在深入到 FreeRTOS 任務管理的源碼之前,鄙人覺得有必要先去從全局的角度進行把握,從全局到局部,從粗線條,到細節,鄙人覺得這樣方可更快的熟悉相關的內部原理;

從全局來看的話,可以先梳理 FreeRTOS 關於任務相關的 APIs,支持的 Feature,以及相關的特性,這樣一來,在深入到源碼級分析的話,知道使用場景,便知道爲何這樣設計;

分析基於 FreeRTOS V 10.3.1

首先,FreeRTOS 任務支持如下特性:

1、多任務執行;

2、支持配置任務優先級;

3、支持任務的阻塞,掛起;

4、任務都是自己的棧空間;

5、支持週期性任務;

6、任務搶佔;

7、協作式調度;

 

1、任務狀態

在單核處理器上,多任務是宏觀並行,微觀串行的;每個任務可以處於不同的狀態:

幾乎所有的 OS 下,任務都分爲了 Ready、Blocked、Running、Suspend 等狀態;這樣劃分是根據具體的使用場景進行的;

Running:指的是正在運行的任務,在單核系統中,同一時刻只有一個任務處於 Running;

Ready:指的是可以被調度運行的任務,也就是處於就緒的任務;

Blocked:指的是因爲某種原因(等待資源,等待時間)暫時不滿足執行條件的任務的狀態;

Suspend:指的是被掛起的任務,暫時不參與調度的狀態;

 

2、任務創建

FreeRTOS 中,創建一個任務使用 xTaskCreate 接口:

BaseType_t xTaskCreate(  TaskFunction_t pvTaskCode,
                         const char * const pcName,
                         unsigned short usStackDepth,
                         void *pvParameters,
                         UBaseType_t uxPriority,
                         TaskHandle_t * pvCreatedTask);

參數的含義如下:

pvTaskCode:任務執行的函數,此函數必須是一個死循環,不會返回。

pcName:任務的名字,是一個字符串;最大長度由宏 configMAX_TASK_NAME_LEN 指定,該宏位於 FreeRTOSConfig.h 文件中。

usStackDepth:任務堆棧的大小,它的單位不是字節,比如在 32-bits 位寬的情況下,這個值設置爲 100,那麼就是分配了 400 字節大小的堆棧。

pvParameters:傳遞給任務執行函數的參數。

uxPriority:任務的優先級。

pvCreatedTask:創建任務成功後的任務句柄,後期可以使用這個句柄來調用任務相關的 API。

返回值的含義如下:

Return:如果創建任務成功,返回 pdPASS 否則返回 pdFAIL

比如:

創建了兩個任務 vTask1 和 vTask2,堆棧深度爲 1000,優先級都爲 1,沒有入參;

創建完後,兩個任務都默認被添加進入了 Ready 狀態,調用 vTaskStartScheduler() 開啓調度器;

它們的實現都是無限循環,執行的時候,進行打印;

它們在時間線上的執行如圖所示:

 

3、任務優先級

在任務初始化的時候,可以指定任務的優先級,同樣在任務運行過程中,也可以通過 API 來改變任務的優先級;

FreeRTOS 支持的最大優先級由 configMAX_PRIORITIES 確定,優先級的值越低,代表優先級越低;0 是最低優先級,所有在 FreeRTOS 中,優先級的範圍處於 0 ~ configMAX_PRIORITIES 之間;

因爲任務存在優先級不一樣,調度器總是選擇處於 Ready 狀態的優先級最高的任務;

比如:

Task 1 優先級是 1,Task 2 優先級是 2;兩個 Task 都是之前的實現的情況下,每次調度器選擇任務的時候,因爲 Task 2 優先級高,每次都會選擇 Task 2,那麼 Task 1 就會被餓死:

 

4、任務阻塞

類似上面的情況,Task 1 被餓死,得不到執行,顯然不是我們想要的,歸根結底,是因爲 Task 2 一直滿足執行條件!按照設計來說,Task 2 優先級高,它應該是去做一些急需快速完成的任務,其他任何事情不準阻攔他,但是哪有任務一直都急需被完成呢?所以,正常情況下,Task 2 可能是在等待某個事件(比如,某個中斷後)或者某個時間點來完成急需完成的任務,其他時間應該處於一種等待的狀態,我們稱之爲阻塞,也就是 Blocked;

在 FreeRTOS 中,有兩種方式可以讓任務進入阻塞狀態:

1、阻塞在時間上:也就是執行任務的時間未到;

2、等待事件:也就是對應的事件還沒有發生;

如果類似上面的代碼,在死循環裏面調用了 vTaskDelay 函數,這就是會導致任務處於阻塞狀態,等時間到了指定的延時後,纔再次進入 Ready 狀態,被調度器調度;

在這種設計下,不同優先級的任務同時運行的時候,就不會有被餓死的情況,同時當兩個任務都沒有執行的時候,時間讓給 IDLE 線程:

5、任務掛起

被掛起的任務,進入 Suspend 狀態,調度器在任務選擇的時候,不再調度進入 Suspend 狀態的任務,除非再次對此任務調用 Resume,重新進入 Ready 隊列,接受調度器的調度;

 

6、空閒任務

在調度器初始化的時候,會創建一個 Idle 任務,這樣可以確保至少有一個任務在運行;此任務優先級最低,爲 0;

空閒任務用來在處理被刪除的任務的內存,所有刪除任務後,一定要確保空閒任務被運行,這樣才能夠內存回收;

FreeRTOS 支持空閒任務的鉤子函數,當開啓 configUSE_IDLE_HOOK 宏是 1 的時候,在空閒任務的時候,函數:

void vApplicationIdleHook( void );

被調用,常用的方式是在此實現低功耗相關的處理邏輯;

 

5、任務調度

5.1、搶佔式調度

FreeRTOS 的任務調度基於週期性的 Tick 心跳,調度器從 Ready 狀態列表中選擇下一個優先級最高的任務投入運行;被阻塞的任務可以通過 event 來臨或者阻塞時間到,重新進入 Ready 狀態;

軟件上,可以通過配置宏,來改變調度算法的行爲,這些宏位於 FreeRTOSConfig.h

configUSE_PREEMPTION:配置爲 1 則說明支持搶佔式調度,否則稱之爲協作式調度;

注:協作式調度需要任務主動放棄 CPU,下一個才能夠被調度;搶佔式調度由系統決定調度;

configUSE_TIME_SLICING :配置爲 1 的時候,同樣優先級的任務會被輪轉調度執行;否則優先級相同的任務,不會被輪轉執行,只會執行其中一個;

最常用的配置是,上述兩個都配置爲 1;

configUSE_PREEMPTION = 1

configUSE_TIME_SLICING = 1

比如有 3 個任務:Task 1、Task 2、Task 3 如下所示,他們的優先級也標記出來;

Task 1 處於 blocked 狀態,等待 event 滿足條件;

Task 2 是週期性任務;

Task 3 是低優先級任務,也處於 blocked 狀態,等待它的 event;

t1 時刻,Task 2 執行,Task 1 和 Task 3 的 event 都沒滿足;

t2 時刻,沒有任務執行,Idle 線程執行;

t3 時刻,Task 3 的 event 滿足了,被調度執行;

t4 時刻,執行 Idle;

t5 時刻,Task 3 的 event 滿足了,被調度執行;

t6 時刻,Task 2 週期性任務來了,優先級高於 Task 3,即便是 Task 3 未執行完,OS 依然調度 Task 2 先執行;

t7 時刻,Task 2 執行完畢,OS 調度 Task 3;

t8 時刻,沒有需要調度的任務,進入 Idle;

t9 時刻,Task 2 週期性任務來了,被調度;

t10時刻,最高優先級的 Task 1 的 event 滿足,即便是 Task 2 未執行完,OS 依然調度 Task 1 執行;

t11 時刻,Task 1 執行完畢,繼續調度尚未執行完畢的 Task 2;此刻低優先級的 Task 3 的 event 雖然也滿足,但是優先級低,執行被推後;

t12 時刻,Task 2 執行完畢,Task 3 得以被調度;

t13 時刻,沒有任務活動,調度 Idle 線程;

 在 configUSE_PREEMPTION 和 configUSE_TIME_SLICING 都配置爲 1 的時候,如果線程和 Idle 線程一樣優先級,那麼他們會被輪轉調度:

上面的調度過程不在多說,Task 1 優先級最高,當滿足他的 event 的時候,搶佔其他任務執行,隨後進入 blocked 狀態;

在上面的例子中(有任務和 Idle 線程一樣連續執行並且優先級一樣),還有一個宏 configIDLE_SHOULD_YIELD,可能會導致調度器行爲變化:

configIDLE_SHOULD_YIELD = 0,那麼行爲和上面的一樣,也是默認情況;

configIDLE_SHOULD_YIELD = 1,那麼 Idle 線程會 loop 一次然後主動讓出 CPU,如下所示:

 

如果 :

configUSE_PREEMPTION = 1

configUSE_TIME_SLICING = 0

這表示調度器支持搶佔,但是對於同樣優先級的任務,不會去輪轉,比如:

可以看到 Task 1 優先級高,可以搶佔低優先級;

但是 Task 2 和 Idle 優先級一樣,但是得不到輪轉的調度;

 

5.2、協作式調度

當配置如下:

configUSE_PREEMPTION = 0

configUSE_TIME_SLICING = any value

代表調度器會進行協作式調度,什麼意思呢?如下所示:

Task 1 優先級最高,Task 2 其次,Task 3 優先級最低;

t1 時刻,Task 3 處於運行,Task 1 和 Task 2 處於阻塞;

t2 時刻,Task 2 滿足運行條件進入 Ready 狀態,但是由於不支持搶佔調度,所以無法執行,Task 3 繼續執行;

t3 時刻,Task 1 滿足運行條件進入 Ready 狀態,但是由於不支持搶佔調度,所以無法執行,Task 3 繼續執行;

t4 時刻,Task 3 主動調用 taskYIELD() 函數,主動放棄 CPU,此刻調度器選擇優先級最高的 Task 1 進行運行;

t5 時刻,Task 1 執行完畢,進入 blocked,調度器調度 Task 2 運行;

t6 時刻,Task 2執行完畢,進入 blocked,調度器調度 Task 3運行;

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章