手把手,嘴對嘴,講解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).
*
*********************************************************************************************************

從上面的說明可以看出,這個函數的作用,主要是用來調度當前已經進入了就緒狀態的最高優先級任務,然后切換進去。

函數定義如下:

 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 }

   關於任務調度的部分肯定是原子操作,不允許任何中斷存在,因此必須要關閉中斷。

   其次,任務調度不能發生在中斷中以及任務調度器上鎖的情況,因此必須加以判定。

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

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()

  函數定義如下:

 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次才能找到就緒的任務,那么時間肯定會很長。

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

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

 

待續……


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM