μC/OS-II與RT-Thread對比——任務調度

       在任務調度器的實現上,μC/OS-II和RT-Thread都採用了位圖調度(bitmap scheduling),任務優先級的值越小則代表具有越高的優先級,主要區別在於實現形式,是採用多級隊列的形式,還是純位圖的形式。在位圖調度下,每當需要進行調度時,從最低位向最高位查找出第一個置 1 的位的所在位置,即爲當前最高優先級,然後從對應優先級就緒隊列獲得相應的任務控制塊,整個調度器的實現複雜度是 O(1),即無論任務多少,其調度時間是固定的。採用多級隊列的方式,一個優先級可以分配給多個任務,任務數不受限制,相同優先級的任務支持協作調度;採用純位圖的實現方式,一個優先級只能分配給一個任務,任務數有限,但確定性更好。

1. 任務控制塊

        任務控制塊是操作系統用於控制任務的一個數據結構,會存放任務相關信息,例如優先級、任務堆棧等等。在RT-Thread中叫做線程控制塊。


(1)μC/OS-II


(2)RT-Thread





        宏觀看上去,μC/OS-II的代碼更規範化,每個定義都是OSTCB開頭的,但是RT-Thread的更清晰明瞭,更具有gcc的編碼風格。從內容上講,兩者都具有最基礎的堆棧指針、優先級、對象鏈表、事件控制域、以及調度需要使用的優先級相關變量,但是RT-Thread還具有一些其他的特性:支持相同優先級的線程鏈表,線程清理函數,線程定時器等等。

2. 任務調度模型

(1)μC/OS-II

       因爲μC/OS-II總是運行進入就緒狀態的最高優先級的任務。所以,確定哪個任務優先級最高,下面該哪個任務運行,這個工作就是由調度器(scheduler)來完成的。任務級的調度是由函數OSSched()完成的,而中斷級的調度是由函數OSIntExt()完成。對於OSSched(),它內部調用的是 OS_TASK_SW()完成實際的調度(人爲模仿一次中斷);OSIntExt()內部調用的是OSCtxSw()實現調度。
 
        

(2)RT-Thread

        RT-Thread中提供的線程調度器是基於優先級的全搶佔式調度:在系統中除了中斷處理函數、調度器上鎖部分的代碼和禁止中斷的代碼是不可搶佔的之外,系統的其他部分都是可以搶佔的,包括線程調度器自身。系統總共支持256個優先級(0 ~ 255,數值越小的優先級越高,0爲最高優先級,255分配給空閒線程使用,一般用戶不使用。在一些資源比較緊張的系統中,可以根據實際情況選擇只支持8個或32個優先級的系統配置)。當系統中,當有比當前線程優先級更高的線程就緒時,當前線程將立刻被換出,高優先級線程搶佔處理器運行。如圖 線程就緒優先級隊列 所示,在RT-Thread調度器的實現中,包含了一個共256個優先級隊列的數組(如果系統最大支持32個優先級,那麼這裏將是一個包含了32個優先級隊列的數組),每個數組元素中放置相同優先級鏈表的表頭。這些相同優先級的列表形成一個雙向環形鏈表,最低優先級線程鏈表一般只包含一個idle線程。

       在優先級隊列1#和2#中,可以看到三個線程:線程A、線程B和線程C。由於線程A、B的優先級比線程C的高,所以此時線程C得不到運行,必須要等待優先級隊列1#的中所有線程(因爲阻塞)都讓出處理器後才能得到執行。同優先級的線程採用時間片輪轉方式進行調度(也就是通常說的分時調度器),時間片輪轉調度僅在當前系統中無更高優先級
就緒線程存在的情況下才有效。例如在 線程就緒優先級隊列 圖中,我們假設線程A和線程B一次最大允許運行的時間片分別是10個時鐘節拍和7個時鐘節拍。那麼線程B將在線程A的時間片結束(10個時鐘節拍)後才能運行,但如果中途線程A被掛起了,即線程A在運行的途中,因爲試圖去持有不可用的資源,而導致線程狀態從就緒狀態更改爲阻塞狀態,那麼線程B會因爲其優先級成爲系統中就緒線程中最高的而馬上運行。每個線程的時間片大小都可以在初始化或創建這個線程時指定。
        RT-Thread實時操作系統提供一系列的操作系統調用接口,使得線程的狀態在這五個狀態之間來回的變換。例如一個就緒態的線程由於申請一個資源(例如使用rt_sem_take),而可能進入掛起態。又例如因爲一個外部中斷髮生了,系統轉入中斷服務例程,在中斷服務例程中釋放了相應的資源,導致把等待在這個資源上的高優先級線程喚醒,改變其狀態爲就緒態,導致當前運行線程切換等等。幾種狀態間的轉換關係如 線程轉換圖 所示:

        線程通過調用函數rt_thread_create/init進入到初始狀態(RT_THREAD_INIT);再通過調用函數rt_thread_startup進入到就緒狀態(RT_THREAD_READY);當處於就緒狀態的線程調用rt_thread_delay,rt_sem_take,rt_mb_recv等函數或由於獲取不到資源時,將進入到掛起狀態(RT_THREAD_SUSPEND);處於掛起狀態的線程,如果等待超時依然未能獲得資源或由於其他線程釋放了資源,那麼它將返回到就緒狀態。掛起狀態的線程,如果調用rt_thread_delete/detach將更改爲關閉狀態(RT_THREAD_CLOSE);而運行狀態的線程,如果運行結束會在線程最後部分執行rt_thread_exit函數而更改爲關閉狀態(RT_THREAD_CLOSE)。

3. 任務調度算法

(1)μC/OS-II

       uCOS II採用內存映射的方式來實現READY隊列的加入,查找,刪除功能,效率非常高。但是也因此只能支持64個任務,每個任務都有自己的優先級,不能和其他任務優先級相同。

        每個任務的就緒態標誌都放入就緒表中的,就緒表中有兩個變量OSRdyGrp和OSRdyTbl[]。(OSRdyGrp佔用一個字節:8位,每位代表一個優先級組;OSRdyTbl[]佔8個字節,共64位,每位代表一個優先級)在OSRdyGrp中,任務按優先級分組,8個任務爲一組。OSRdyGrp中的每一位表示8組任務中每一組中是否有進入就緒態的任務。任務進入就緒態時,就緒表OSRdyTbl[]中的相應元素的相應位也置位。就緒表OSRdyTbl[]數組的大小取決於OS_LOWEST_PRIO(見文件OS_CFG.H)。

     爲確定下次該哪個優先級的任務運行了,內核調度器總是將OS_LOWEST_PRIO在就緒表中相應字節的相應位置1。OSRdyGrp和OSRdyTbl[]的關係見圖3.3,是按以下規則給出的: 當OSRdyTbl[0]中的任何一位是1時,OSRdyGrp的第0位置1,
    當OSRdyTbl[1]中的任何一位是1時,OSRdyGrp的第1位置1,
    當OSRdyTbl[2]中的任何一位是1時,OSRdyGrp的第2位置1,
    以些類推。。。。。
    任務就緒的掩碼錶:
    INT8U const OSMapTbl[]   = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};//定位優先級所用

    任務優先級的低三位用於確定任務在總就緒表OSRdyTbl[]中的所在位。接下去的[5:3]三位用於確定是在OSRdyTbl[] 數組的第幾個元素。 OSMapTbl[]是在ROM中的(見文件OS_CORE.C)屏蔽字,用於限制OSRdyTbl[]數組的元素下標在0到7之間:

        下面的代碼用於將任務放入就緒表,使任務進入就緒態 (這兩行代碼是關鍵)。Prio是任務的優先級。

       這行代碼功能是找到組, 把組上的值置爲1。不妨假設prio的值爲13, 即優先級爲13. prio>>3 右移3位後值爲1, 可以查表T3.1找出OSMapTbl[1] 的值爲 0000 0010. 再用 0000 0010 和 OSRdyGrp 進行異或運算:
OSRdyGrp |= OSMapTbl[prio >> 3];   //先根據高3位[5:3]找到所在的組
OSRdyTbl[prio >> 3] |= OSMapTbl[prio & 0x07]; //再由低3位把該組的具體某一位置1

      如果一個任務被刪除了,則用下邊的代碼做求反處理,從就緒表中刪除一個任務

if ((OSRdyTbl[prio >> 3] &= ~OSMapTbl[prio & 0x07]) == 0)
    OSRdyGrp &= ~OSMapTbl[prio >> 3];

       以上代碼將就緒任務表數組OSRdyTbl[]中相應元素的相應位清零,而對於OSRdyGrp,只有當被刪除任務所在任務組中全組任務一個都沒有進入就緒態時,纔將相應位清零。也就是說OSRdyTbl[prio>>3]所有的位都是零時,OSRdyGrp的相應位才清零。爲了找到那個進入就緒態的優先級最高的任務,並不需要從OSRdyTbl[0]開始掃描整個就緒任務表,只需要查另外一張表,即優先級判定表 OSUnMapTbl ([256])(見文件OS_CORE.C)。OSRdyTbl[]中每個字節的8位代表這一組的8個任務哪些進入就緒態了,低位的優先級高於高位。利用這個字節爲下標來查OSUnMapTbl這張表,返回的字節就是該組任務中就緒態任務中優先級最高的那個任務所在的位置(即該字節的中最低位1的所在位)。這個返回值在0到7之間。確定進入就緒態的優先級最高的任務是用以下代碼完成的。
       找出進入就緒態的優先級最高的任務

y    = OSUnMapTbl[OSRdyGrp];
x    = OSUnMapTbl[OSRdyTbl[y]];
prio = (y << 3) + x;
       優先級查詢表:
INT8U const OSUnMapTbl[] = {
    0, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0
};//定位最高優先級用(即查找一個字節的最低位1所在的位置)
     例如,如果OSRdyGrp的值爲二進制01101000,查OSUnMapTbl[OSRdyGrp]得到的值是3,它相應於OSRdyGrp中的第3 位bit3,這裏假設最右邊的一位是第0位bit0。類似地,如果OSRdyTbl[3]的值是二進制11100100,則OSUnMapTbl [OSRdyTbc[3]]的值是2,即第2位。於是任務的優先級Prio就等於26(3*8+2)。利用這個優先級的值。查任務控制塊優先級表 OSTCBPrioTbl[],得到指向相應任務的任務控制塊OS_TCB的工作就完成了。

(2)RT-Thread

        RT-Thread的調度算法與μC/OS-II基本是一樣的,都是位圖調度,但是在這個基礎上有一定的擴展,因爲在RT-Thread系統存在多個線程時,可能的情況是,某些線程具有不同的線程優先級,但是還有一些線程具有相同的優先級。對於這種情況,rt-thread採用的調度策略是,對不同優先級的線程,採用可搶佔的方式:即高優先級的線程會“立刻”搶佔低優先級的線程,而對同線程優先級別的多個線程則採用時間片輪轉的方式。
       首先來考慮,每一個優先級上是否存在線程,這是一個是/否問題,即要麼存在線程,要麼不存在線程,這可以用一個bit位來表示。我們規定這個bit爲1表示存在線程,爲0表示不存在線程。對於256級的線程,則共需要256個bit位。這個位圖要比μC/OS-II複雜一些,如下:
       單元格中的內容表示對應的優先級。 每一行爲對應的一個字節,每一列爲各個bit位。

       這張圖就是前面scheduler.c中定義的32個字節的數組 (rt_uint8_t rt_thread_ready_table[32])。
       舉個例子,我們創建了一個線程,並且指定了它的優先級是125,然後將它設置爲就緒(READY),實際上在我們在調用函數將它變爲READY的函數中,RTT就會去上面這256個bit中(也即是這32個字節),找到第125個bit,我稱之爲位圖的BIT125, 也即是字節15 (125/ 8 = 15,125%8 = 5)的第5個bit,將這個bit置1。 即位圖的BIT125 就是rt_thread_ready_table[125/8]的BIT5.我們可以用位代碼表示爲 BITMPA.BIT_125 = rt_thread_ready_table[125/8].BIT5
       優先級125 對應那個字節的哪個bit呢?
       這裏有個換算關係。其計算公式 :
       (優先級別 / 8 )商取整數即對應位圖中的字節 (優先級別 % 8 )就是對應位圖字節中的bit位,即優先級125, 125 / 8 = 15 , 125 %8 = 5. 位圖的BIT125就是 rt_thread_ready_table[15]的BIT5
       現在我們將位圖看作一個變量,並假定當前優先級別爲8,則位圖變量可以用一個字節表示。此時和μC/OS-II的算法是一樣的,同樣設置一個查找表,實際上,查表法就是一種常用的用空間換取時間的方法。

        進程優先級爲8時,我們可以查表直接解決,當系統存在32個優先級時,如果直接製作表格的話,這個表格的元素個數將是 2**32 = 4294967296L= 4G字節。顯然這是不可接受的。
        32個優先級,即優先級位圖變量可以使用u32型,也就是等價於4個字節,我們可以對這4個字節從字節0開始依次查表,如果字節0中非0,則最高優先級一定存在於字節0中,我們對字節0查表rt_lowest_bitmap,即可以得到最高優先級。 如果字節0爲0,字節1非0,我們對字節1查表得到的是字節1中爲1的最低bit位,然後加上8,就是系統的最高優先級。對字節2,字節3同樣處理。
        假定當前u32 rt_thread_priority_bitmap維護着當前系統優先級位圖。

       現在我們解決了32個系統優先級時的調度問題,現在來考慮線程優先級爲256的情況。讀者可能會想了,這沒什麼不同,256個bit=32個字節,依然採用算法3的思路,對着32個字節依次查表。問題是,當位圖變量有32個字節時,對這32個字節依次查表耗費的時間就不可以忽略了,爲了提升系統實時調度的性能,我們需要對算法3進行改進。
       爲了解決這個問題,我們使用二級位圖。即,256個bit由32個字節存儲,每一個字節的8個bit代表着位圖變量中的8個優先級,如果某個字節非0,則表示其中必有非0的bit位。
       rtt中對應的數組爲rt_uint8_t rt_thread_ready_table[32]
       所謂二級位圖,即我們先確定32個字節中最低的非0的字節。爲了實現這個效果,我們需要對這32個字節引入一個32個bit的位圖變量,每一個bit位表示對應的字節是否爲0。例如,這個32bit的位圖變量的BIT5爲0,表示系統線程優先級256bit所分成的32個字節中的 字節5 非0。 爲了區分,稱這個32個bit的位圖變量 字節位圖變量 ,rt-thread中使用的是rt_thread_ready_priority_group. 顯然我們查找系統系統最高優先級時,先確定非0的最低字節,這實際上依然是算法3,然後再對該字節進行查表,即得到該字節內最低爲1的bit位,然後兩者疊加(注意不是簡單的加)即可。
       根據上面的分析,要想使用這個二級位圖算法,rtt在跟蹤線程的狀態轉換時,不僅需要維護256bit的位圖變量數組rt_thread_ready_table[thread->number] |= thread->high_mask,還需要維護32bit的 字節位圖變量 rt_thread_ready_priority_group。參看如下代碼。

       初始化線程時,我們指定了一個線程的優先級別thread->init_priority,由於線程優先級爲0到255,一個字節就可以表示。但是我們的bitmap是32個字節。爲了調高效率,我們最好能快速向位圖的對應的bit寫1。
       語句(1)thread->current_priority >> 3,這裏的>>3就是除以8,因爲一個字節表示8個優先級。這樣就可以得到當前這個優先級對應的位圖32個字節中的第幾個字節,這裏用thread->number表示,顯然,number範圍是0到31。這裏爲了提高效率,採用移位完成除法。
       上面除法的餘數,就表示這個優先級在上面字節中的第幾個bit。這個餘數可以使用 (thread->current_priority & 0x07)來表示。
       語句(3)是得到該bit對應的權值。例如一個字節的bit7對應的權值即 (1<<7),這樣做是爲了使用“位與,或,非”等位運算,可以提高運行速度,即語句(4)。
       語句(4)清楚表示了這幾個變量作用。可見,根據某個表示優先級的數字向位圖中相應的bit位寫入了1。
       那麼語句(2)和(5)是做什麼用的呢? 這個number_mask實際上是爲了加快查找位圖的速度而創建的。它將在rt_schedule函數中發揮作用。
      上文已說明,thread->number就表示當前線程優先級在32個字節的位圖數組中的字節位置。爲了提高效率,rt-thread另外使用了一個u32類型的變量rt_thread_ready_priority_group 來加快速度。如果這32個bit中某一個bit爲1,就表示對應的某個字節非0(想想看,這意味着該字節所表示的8個優先級中存在就緒線程)。
       rt_thread_ready_priority_group變量爲32位寬度,長度上等於4個字節,因此可以對每一個字節查表(上面生成的表格)就可以得到爲1的最低的bit位置。
       概括起來就是,rtt首先確定32個字節的位圖中,非0的最低的那個字節,然後再查表得到這個字節非0的最低那個bit。這兩步驟正好可以利用兩次上面的表格rt_lowest_bitmap。
      下面附上rt_schedule的代碼。非必要的代碼被我隱去。讀者可以對比下面的代碼理解上面的思路。


4. 總結

       從上面的對比中可以看出, RT-Thread與μC/OS-II在任務調度方面的算法基本一致,複雜度都爲O(1),但是前者通過二級位圖支持更多的優先級,同時在支持優先級相同的任務採用時間片輪轉的方式進行調度。

參考文獻:
1.《RT-Thread編程手冊》
2.《嵌入式實時操作系統uCOS-II》
3. rt-thread的位圖調度算法分析:http://blog.csdn.net/hcx25909/article/details/18009535
4. UCOS之任務調度機制 : http://tianwaike1.blog.163.com/blog/static/351366792009911101830325/

----------------------------------------------------------------

歡迎大家轉載我的文章。

轉載請註明:轉自古-月

http://blog.csdn.net/hcx25909

歡迎繼續關注我的博客


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