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次才能找到就緒的任務,那么時間肯定會很長。
用這種方法會導致尋找就緒任務需要的時間完全不能確定,有時候短,有時候長,然而這對於一個系統而言,最怕的就是這種不確定因素。
查表法和循環法就完全不同了,它雖然死板一些,但不管當前系統有多少任務,不管當前有多少任務是處於就緒狀態,它每次計算出最高優先級的任務的時間是一定的,這種確定性對於系統很重要。