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