*本文依據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中斷時對當前任務的時間片減一,然后檢查其它任務的時間片剩余情況。一旦當前任務的時間片用完,則會先重置當前任務的時間片。然后看是否有想同優先級的任務,如果有則會將當前任務移到隊列末尾。然后觸發優先級調度,此時只要當前優先級是已就緒的最高優先級最終就會取出相同優先級隊列頭的任務運行。拋開其它因素簡單來說就是只要當前任務的時間片用完了,則會將當前任務移到隊列末尾,下一個任務自然而然處於隊列頭將獲得運行。所以這就看起來是每個任務輪流來運行,只是每個任務的運行時間長短不一樣而已,這個運行的時間長短就是由時間片指定的。
綜上所述,體現時間片輪轉調度的前提是建立多個相同優先級的任務。因為時間片輪轉調度只會發生在相同優先級的任務之間。否則可以認為系統中只存在優先級調度。
下圖展示了三個相同優先級任務的時間片輪轉調度運行情況:
感謝各位網友的支持,可以關注我的微信公眾號:鵬城碼夫 (微信號:rocotona)