1. 程式人生 > >原始碼解讀·RT-Thread多工排程演算法

原始碼解讀·RT-Thread多工排程演算法

*本文依據RT-Thread當時最新版本4.0.1版本原始碼

RT-Thread作業系統是一款基於優先順序和時間片輪轉的多工實時作業系統。其排程演算法採用256個優先順序,並支援相同優先順序的任務存在。不同優先順序的任務採用優先順序排程,而相同優先順序的任務則採用時間片輪轉排程。其實這種排程演算法在絕大多數系統中都一樣,像我知道的μCos和freertos都是如此。不過這裡需要先了解一個問題,也是我初學時被困擾的問題——多種排程演算法存在時那麼何時採用何種排程演算法?彼此又是如何共存和協調進行的?這要等看完並看懂排程演算法的原始碼之後才算明白其中原理。其實排程演算法採用優先順序排程為主要依據,以時間片輪轉為次要依據。也就是說只當沒有更高優先順序任務就緒的情況下,想同優先順序任務之間的排程才會採用時間片輪轉排程。

優先順序排程

優先順序在多工排程中是什麼?優先順序其實是給任務分配的一個數值,數值越小則優先順序越高。優先順序的高低將直接反應在任務排程演算法中,優先順序越高越優先響應。

在RT-Thread中優先順序排程演算法支援256個優先順序,可以通過巨集定義配置。通常情況下裁剪過程中會根據需要來定義優先順序數量。不過在原始碼中只會體現出兩種不同優先順序數量的差異,分別為32個和32個以上。RT-Thread採用bitmap演算法來計算優先順序。bitmap演算法是二進位制與位運算完美結合的體現。看懂程式碼之後相信大家都會來一句“臥槽,既然還可以這樣操作!”,真的崇拜發明bitmap演算法的大佬,不過我並不知道是誰最先發明的,第一次接觸是在學習μCos的時候。

前面提到過,RT-Thread根據優先順序的數量不同分為兩種bitmap演算法(優先順序32個和32個以上),程式碼稍微有些差異,主要是為了優化資源佔用。其中不超過32個優先順序的情況下只會用一個32bit的變數,超過32個優先順序後會使用一個長度為32個元素的byte陣列,外加一個32bit的變數用來分組。其中無論多少個優先順序,每個優先順序都只需要用一個bit來表示對應優先順序的任務是否就緒狀態(為1表示就緒,為0表示掛起),所以最多支援256個優先順序。

bitmap演算法

為了理解優先順序計算中使用的bitmap演算法,首先必須要先掌握十進位制與二進位制的轉換,並且還需要掌握位運算。以RT-Thread中優先順序大於32個的情況為例來說明,其32個及一下優先順序數量的方式更簡單,稍後會簡單說明。先看RT-Thread中的原始碼,關於bitmap演算法會用到的幾個變數:

rt_list_t rt_thread_priority_table[RT_THREAD_PRIORITY_MAX];
struct rt_thread *rt_current_thread;
rt_uint8_t rt_current_priority;

#if RT_THREAD_PRIORITY_MAX > 32
    /* Maximum priority level, 256 */
    rt_uint32_t rt_thread_ready_priority_group;
    rt_uint8_trt_thread_ready_table[32];
#else
    /* Maximum priority level, 32 */
    rt_uint32_t rt_thread_ready_priority_group;
#endif

 

RT-Thread中當優先順序大於32個的情況下,將任務優先順序分為32組,每組8個。其分組用rt_thread_ready_priority_group變數來管理,這是一個32bit的變數,每一個bit代表一個組的就緒態。而每一組中的8個優先順序則用rt_thread_ready_table陣列來管理,其每個元素佔用一個8bit大小,同樣每個bit代表一個優先順序。在原始碼中優先順序從0開始最大到255,所以具體的分組情況就是每8個為一組用一個bit來表示,依次分為32組共需要32個bit來表示分組,也就是rt_thread_ready_priority_group變數。當某一組中對應的優先順序任務處於就緒態則對應的bit將被置1.例如優先順序為19的任務處於就緒,則根據分組情況其處於第三組(16至23),所以rt_thread_ready_priority_group中的第三bit將置1(第三個bit也就是bit2,因為通常習慣將bit從0開始數).這就在後續的排程演算法過程中排程器知道此組中有任務處於就緒了。更進一步的細節是,還需要在rt_thread_ready_table中第三個元素(下標為2)中的第四bit(bit3)置1(因為19在第三組16,17,18,19中位於第四個).這樣就準確的標識了唯一的優先順序號。

那如果同時有多個組中的任務都處於就緒該怎麼計算一個最高優先順序呢?如果存在同時處於就緒的任務則對應的分組bit都會在rt_thread_ready_priority_group中置1,且同時在rt_thread_ready_table中對應的組中的bit也會置1.前面提到過優先順序越高其優先順序編號越小,比如優先順序0是最高的優先順序,優先順序255是最低優先順序。這就可以推理得知越小的優先順序編號就對應在rt_thread_ready_priority_group越低的bit上。不難看出0至7優先順序對應rt_thread_ready_priority_group變數的bit0,同理8至15對應比bit2,16至23對應bit3.等等。現假設優先順序5和19的任務處於就緒態,那麼其rt_thread_ready_priority_group變數的bit0和bit2將被置1,同時rt_thread_ready_table[0]的bit5以及rt_thread_ready_table[2]的bit3將被置1.如下圖所示: 

 

 

 

如果此時排程器排程時,應該要計算出優先順序為5的任務來執行。這其實分了三步來計算的。

首先拿rt_thread_ready_priority_group變數利用ffs函式計算最低位為1的bit是第幾個bit。顯然這個例子中是第一個bit0位為1,假如這個結果我們用叫index的變數暫存起來,那麼index等於1.

第二步利用第一步計算的結果index作為rt_thread_ready_table的索引(索引從0開始為第一個,所以要index-1),即rt_thread_ready_table[0]。再一次做ffs(rt_thread_ready_table[0])計算最低位為1的bit是第幾位,顯然例子是bit5,假如我們再用個變數offset儲存這個值,那麼offset等於6。

第三步根據前兩步計算出來的index和offset得出最終的最高優先順序為(index-1)*8+(offset-1),這裡的乘法可以用位運算代替,所以等價於((index-1) <<3) + (offset-1).其真正的RT-Thread原始碼如下:

register rt_ubase_t highest_ready_priority;

#if RT_THREAD_PRIORITY_MAX <= 32
    highest_ready_priority = __rt_ffs(rt_thread_ready_priority_group) - 1;

#else
    register rt_ubase_t number;

    number = __rt_ffs(rt_thread_ready_priority_group) -1;
    highest_ready_priority = (number << 3) +__rt_ffs(rt_thread_ready_table[number]) - 1;
#endif

 

其上面的程式碼__rt_ffs返回值就如上例的index變數,做了減1操作是因為索引和bit都從0開始。highest_ready_priority即計算出來的最高優先順序。

關於ffs函式的實現細節可以看RT-Thread原始碼裡的各種實現方式,有C函式實現,也有針對各種編譯器和處理器優化的特殊指令的實現。不過其功能就是計算一個值二進位制位為1的最低位是第幾位。在RT-Thread的C函式實現中做了一個0到255的索引陣列,其陣列的值分別就是0到255這些數值所對應的二進位制位為1的最低位索引。

最後說明一下,如上面的程式碼所示,當優先順序數定義為不超過32個時,就不存在rt_thread_ready_table了,更節省資源。也可以理解為每組只有一個優先順序,所以可以直接用rt_thread_ready_priority_group直接代替了。因為最多才32個優先順序,rt_thread_ready_priority_group剛好32bit,每個bit代表一個優先順序剛好對應上。

時間片輪轉排程

在說明時間片輪轉排程前,先要說明一下什麼是時間片。在作業系統裡,時間片的概念是相對於作業系統的TICK中斷的。每觸發一次TICK中斷就相當於一個時間片。

時間片輪轉排程會在每個TICK中斷時對當前任務的時間片減一,然後檢查其它任務的時間片剩餘情況。一旦當前任務的時間片用完,則會先重置當前任務的時間片。然後看是否有想同優先順序的任務,如果有則會將當前任務移到佇列末尾。然後觸發優先順序排程,此時只要當前優先順序是已就緒的最高優先順序最終就會取出相同優先順序佇列頭的任務執行。拋開其它因素簡單來說就是隻要當前任務的時間片用完了,則會將當前任務移到佇列末尾,下一個任務自然而然處於佇列頭將獲得執行。所以這就看起來是每個任務輪流來執行,只是每個任務的執行時間長短不一樣而已,這個執行的時間長短就是由時間片指定的。

綜上所述,體現時間片輪轉排程的前提是建立多個相同優先順序的任務。因為時間片輪轉排程只會發生在相同優先順序的任務之間。否則可以認為系統中只存在優先順序排程。

下圖展示了三個相同優先順序任務的時間片輪轉排程執行情況:

 

&n