手把手,嘴對嘴,講解UCOSII嵌入式操做系統的任務調度策略(三)

if (ticks > 0u) {                            /* 0 means no delay!                                  */
        OS_ENTER_CRITICAL();
        y            =  OSTCBCur->OSTCBY;        /* Delay current task                                 */
        OSRdyTbl[y] &= (OS_PRIO)~OSTCBCur->OSTCBBitX;
        if (OSRdyTbl[y] == 0u) {
            OSRdyGrp &= (OS_PRIO)~OSTCBCur->OSTCBBitY;
        }
        OSTCBCur->OSTCBDly = ticks;              /* Load ticks in TCB                                  */
        OS_EXIT_CRITICAL();
        OS_Sched();                              /* Find next task to run!                             */
    }

依然是這一部分,接下來的重點是這個函數:OS_Sched()算法

這個函數實在是過重要了,所以我不得不慎重。數組

首先看一下官方的註釋:函數

*********************************************************************************************************
* SCHEDULER
*
* Description: This function is called by other uC/OS-II services to determine whether a new, high
* priority task has been made ready to run. This function is invoked by TASK level code
* and is not used to reschedule tasks from ISRs (see OSIntExit() for ISR rescheduling).
*
*********************************************************************************************************this

從上面的說明能夠看出,這個函數的做用,主要是用來調度當前已經進入了就緒狀態的最高優先級任務,而後切換進去。spa

函數定義以下:code

 1 void  OS_Sched (void)
 2 {
 3 #if OS_CRITICAL_METHOD == 3u                           /* Allocate storage for CPU status register     */
 4     OS_CPU_SR  cpu_sr = 0u;
 5 #endif
 6 
 9     OS_ENTER_CRITICAL();
10     if (OSIntNesting == 0u) {                          /* Schedule only if all ISRs done and ...       */
11         if (OSLockNesting == 0u) {                     /* ... scheduler is not locked                  */
12             OS_SchedNew();
13             OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
14             if (OSPrioHighRdy != OSPrioCur) {          /* No Ctx Sw if current task is highest rdy     */
15 #if OS_TASK_PROFILE_EN > 0u
16                 OSTCBHighRdy->OSTCBCtxSwCtr++;         /* Inc. # of context switches to this task      */
17 #endif
18                 OSCtxSwCtr++;                          /* Increment context switch counter             */
19                 OS_TASK_SW();                          /* Perform a context switch                     */
20             }
21         }
22     }
23     OS_EXIT_CRITICAL();
24 }

   關於任務調度的部分確定是原子操做,不容許任何中斷存在,所以必需要關閉中斷。orm

   其次,任務調度不能發生在中斷中以及任務調度器上鎖的狀況,所以必須加以斷定。blog

  上面的內容比較簡單,去掉那些判斷和宏開關,咱們須要關注的重點主要在如下的部分:ip

1             OS_SchedNew();
2             OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
3             if (OSPrioHighRdy != OSPrioCur) {          /* No Ctx Sw if current task is highest rdy     */
5                 OS_TASK_SW();                          /* Perform a context switch                     */
6             }

  首先看這個函數:OS_SchedNew()rem

  函數定義以下:

 1 static  void  OS_SchedNew (void)
 2 {
 3 #if OS_LOWEST_PRIO <= 63u                        /* See if we support up to 64 tasks                   */
 4     INT8U   y;
 5 
 6 
 7     y             = OSUnMapTbl[OSRdyGrp];
 8     OSPrioHighRdy = (INT8U)((y << 3u) + OSUnMapTbl[OSRdyTbl[y]]);
 9 #else                                            /* We support up to 256 tasks                         */
10     INT8U     y;
11     OS_PRIO  *ptbl;
12 
13 
14     if ((OSRdyGrp & 0xFFu) != 0u) {
15         y = OSUnMapTbl[OSRdyGrp & 0xFFu];
16     } else {
17         y = OSUnMapTbl[(OS_PRIO)(OSRdyGrp >> 8u) & 0xFFu] + 8u;
18     }
19     ptbl = &OSRdyTbl[y];
20     if ((*ptbl & 0xFFu) != 0u) {
21         OSPrioHighRdy = (INT8U)((y << 4u) + OSUnMapTbl[(*ptbl & 0xFFu)]);
22     } else {
23         OSPrioHighRdy = (INT8U)((y << 4u) + OSUnMapTbl[(OS_PRIO)(*ptbl >> 8u) & 0xFFu] + 8u);
24     }
25 #endif
26 }

由於咱們的系統最高支持64個任務,因此去掉那些咱們不須要關注的地方,函數定義簡化以下:

static  void  OS_SchedNew (void)
{
    INT8U   y;

    y  = OSUnMapTbl[OSRdyGrp];
    OSPrioHighRdy = (INT8U)((y << 3u) + OSUnMapTbl[OSRdyTbl[y]]);
}

看着好像很簡單,整個函數就兩句話,可是,只要能把這兩句話給弄明白了,關於調度的東西基本上就都沒問題了。

關於變量OSRdyGrp與數組OSRdyTbl[]的意義,相信各位都已經十分理解,分別表明組就緒狀態和任務就緒狀態,那麼新出來的這個數組OSUnMapTbl[]又表明什麼呢?它和任務就緒表有什麼關係?

跟蹤OSUnMapTbl數組的定義能夠發現,這是一個常數數組,它裏面的內容是隻讀的,定義以下:

INT8U  const  OSUnMapTbl[256] = {
    0u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x00 to 0x0F                   */
    4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x10 to 0x1F                   */
    5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x20 to 0x2F                   */
    4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x30 to 0x3F                   */
    6u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x40 to 0x4F                   */
    4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x50 to 0x5F                   */
    5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x60 to 0x6F                   */
    4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x70 to 0x7F                   */
    7u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x80 to 0x8F                   */
    4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0x90 to 0x9F                   */
    5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xA0 to 0xAF                   */
    4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xB0 to 0xBF                   */
    6u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xC0 to 0xCF                   */
    4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xD0 to 0xDF                   */
    5u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, /* 0xE0 to 0xEF                   */
    4u, 0u, 1u, 0u, 2u, 0u, 1u, 0u, 3u, 0u, 1u, 0u, 2u, 0u, 1u, 0u  /* 0xF0 to 0xFF                   */
};

這個表還不小,一眼看去腦殼都大了,它究竟是個什麼玩意兒?別慌,聽我慢慢講解……

依然舉個例子,如今咱們的系統只有兩個任務(0和12),當前的任務優先級是0,而後這個任務進入了延時,這個時候根據前面瞭解的東西:

        y            =  OSTCBCur->OSTCBY;        /* Delay current task                                 */
        OSRdyTbl[y] &= (OS_PRIO)~OSTCBCur->OSTCBBitX;

這個優先級爲0的任務已經被設置爲了未就緒狀態,也就是把它的就緒表清空了,對應的OSRdyTbl[0]確定是0,因爲只有兩個任務,所以對應的OSRdyGrp的最後一個bit位,也確定是0,。

而後咱們還有一個優先級爲12的任務已經準備就緒, 那麼代碼執行到了這裏,任務的OSRdyTbl[1]必然等於0x10,組號OSRdyGrp必然等於0x02,把2帶進這個常數表,獲得的結果是:1

這個「1」有什麼意義?

    y             = OSUnMapTbl[OSRdyGrp];
    OSPrioHighRdy = (INT8U)((y << 3u) + OSUnMapTbl[OSRdyTbl[y]]);

結論:這兩句代碼真正的功能,就是從系統中,把當前已經就緒了的任務裏,優先級最高的那個任務給找出來,而這個任務也就是我接下來要切換進去的那一個

假設如今咱們系統中只有兩個任務,一個優先級爲0(未就緒),一個優先級爲12(就緒),如今咱們就來看看,他究竟是怎麼把12這個數據給找出來的。

上面說了,當一個任務的優先級是肯定數的時候,他的組號、組內坐席號,偏移量等都是肯定的。

當只有任務12就緒時,這個時候組號OSRdyGrp必然等於0x02,那麼把它帶入那個常數表中,獲得結果y爲1。

在把1帶入數組OSRdyTbl[1]中,等到結果的結果是0x10(參考上一節),把0x10帶入常數表,獲得的結果OSUnMapTbl[OSRdyTbl[y]] == 4

這個y等於1,把它向左移動3個bit,獲得的結果是8(二進制00001000)……最後的結果8 + 4 = 12

沒想到它真的把我須要的優先級給算出來了,究竟是怎麼作到的?

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

其實,UCOSII使用的這種方法被叫查表法,根據必定的規律,直接計算出當前優先級最高的那個任務號。

那個看似莫名其妙的常數表OSUnMapTbl,其實它所表明的意思,是0~255個數字中,1所在的最低位:

  好比1,二進制00000001,在它的最低位出現了1,那麼帶入常數表一查,發現OSUnMapTbl[1] = 0,也就是第0位出現了1。

  好比2,二進制00000010,在它的次低位出現了1,那麼帶入常數表一查,發現OSUnMapTbl[2] = 1,也就是第1位出現了1。

  好比3,二進制00000011,在它的最低位出現了1,那麼帶入常數表一查,發現OSUnMapTbl[3] = 0,也就是第0位出現了1。

  ……

  好比12,二進制00001100,在它的第2位出現了1,那麼帶入常數表一查,發現OSUnMapTbl[12] = 2,也就是第2位出現了1。

  好比63,二進制011111111,在它的最低位出現了1,那麼帶入常數表一查,發現OSUnMapTbl[3] = 0,也就是第0位出現了1。

由於有了這個表,算法上纔可能作到不管有多少個任務進入了就緒的狀態,我都能輕輕鬆鬆地取出優先級最高的那一個,根據變量OSRdyGrp的狀態,我能夠找到那些組有就緒的任務,

若是第0組和第3組內都有就緒的任務,那麼OSRdyGrp確定等於0x05,可是我根本不用關心第3組的狀態,忽略便可,由於第0組明顯優先程度更大,我只須要繼續前往第0組內尋找即可。

進入第0組內部後,假如優先級爲1和優先級爲5的任務都就緒了,變量OSRdyTbl[0] == 0x22(二進制00100010),我確定要執行優先級爲1的任務,那麼我就直接把這個數據帶進那個常數表中去查尋,看看最低位出現1的位置,也就是就緒的任務究竟是哪個,

只要找到了它,別的任務就算是就緒狀態,我也不用管了……查表法即是基於這個原理。

    y             = OSUnMapTbl[OSRdyGrp];
    OSPrioHighRdy = (INT8U)((y << 3u) + OSUnMapTbl[OSRdyTbl[y]]);

再看一下這兩句代碼,第一句代碼的意思是:找到就緒任務中優先級最高的組號,好比1組和3組都就緒了,我須要的結果是:1。

第二句代碼中的這句話OSUnMapTbl[OSRdyTbl[y]]的意思是:找到在這個組中,優先級最高的任務的坐席號,也就是偏移量,好比任務12和任務13都就緒了,我須要的結果是:4(表明任務12的偏移)。

整個第二個代碼的意思是:把組號和組內坐席號組合起來,造成最後的任務優先級。

 

若是想不明白的話,能夠參考上一章:

       ptcb->OSTCBY             = (INT8U)(prio >> 3u);
       ptcb->OSTCBX             = (INT8U)(prio & 0x07u);

這兩句話的意思,在創建任務的時候,把一個好好的優先級給拆開,如今終因而把它們給從新合上了。

 

如今回過頭看,OS_SchedNew這個函數的做用是什麼?

很明顯,它的做用就是尋找到,在當前已經就緒的任務中,優先級最高的那一個任務。

 

題外話,思考一個問題,爲何要用查表法呢?

若是是本身來作這個策略,有沒有其餘的方法?

固然有,若是是我來作,或許能夠創建一個大表,裏面裝有全部任務的就緒狀態,而後寫一個for循環,每次進行任務切換的時候,從低到高依次判斷,若是任務狀態位bit是1,那麼就證實這個任務是就緒了的,當即跳出去進行任務切換,若是任務狀態bit是0,那就證實這個任務沒有就緒,繼續進行下一個判斷,我想這樣確定更容易理解一些。

不過這樣作有一個問題,若是當前我就緒的任務優先級是0,那麼在第一個循環就能找到任務,而後任務切換,時間比查表法塊不少,若是我就緒的任務是255呢?那麼我可能就須要循環255次才能找到就緒的任務,那麼時間確定會很長。

用這種方法會致使尋找就緒任務須要的時間徹底不能肯定,有時候短,有時候長,然而這對於一個系統而言,最怕的就是這種不肯定因素。

查表法和循環法就徹底不一樣了,它雖然死板一些,但無論當前系統有多少任務,無論當前有多少任務是處於就緒狀態,它每次計算出最高優先級的任務的時間是必定的,這種肯定性對於系統很重要。

 

待續……

相關文章
相關標籤/搜索