ucos_ii 作為一個實時系統,最主要的任務就是為了實現任務的調度,為了實現任務的調度,使用了任務就緒表的方法來供ucos來查詢(實時性)最高優先級的任務,並且切換到最高優先級任務去執行。
注意兩個地方:
第二 任務的創建或者是其他需要任務切換過程中,就緒表就會得到更新,並供ucos服務程序os_sched()查詢
第一 為了滿足時間確定性,所以不能夠使用for循環的遍歷方式去遍歷就緒表以找到最高優先級的任務,所以此處用了查找表的方式
1、就緒表的基礎
系統存在多個任務(對於不同的版本任務個數不同,此處的任務個數最多不超過64個),ucos在做任務調度的時候,需要知道那些任務已經准備好可以運行,並且還要知道最高優先級的任務是哪個。
ucos使用就緒表的方式來實現這一功能。當前的ucos總共有64個任務,將64個任務分成8組來表征,每一組中用一個Uint8類型的數據的每一位來表征8個task。那么總共就可以表征64個任務。
ucos設定了兩個變量來表征就緒表
uint8 OSRdyGrp;
uint8 OSRdyTbl[] ;
OSRdyTbl[]_bit0 | OSRdyTbl[]_bit1 | OSRdyTbl[]_bit2 | OSRdyTbl[]_bit3 | OSRdyTbl[]_bit4 | OSRdyTbl[]_bit5 | OSRdyTbl[]_bit6 | OSRdyTbl[]_bit7 | |
OSRdyGrp_bit0 | task_pri_0 | task_pri_1 | task_pri_2 | task_pri_3 | task_pri_4 | task_pri_5 | task_pri_6 | task_pri_7 |
OSRdyGrp_bit1 | task_pri_8 | task_pri_9 | task_pri_15 | |||||
OSRdyGrp_bit2 | task_pri_16 | task_pri_17 | task_pri_20 | task_pri_23 | ||||
OSRdyGrp_bit3 | task_pri_24 | task_pri_25 | task_pri_31 | |||||
OSRdyGrp_bit4 | task_pri_32 | task_pri_33 | task_pri_39 | |||||
OSRdyGrp_bit5 | task_pri_40 | task_pri_41 | task_pri_47 | |||||
OSRdyGrp_bit6 | task_pri_48 | task_pri_49 | task_pri_55 | |||||
OSRdyGrp_bit7 | task_pri_56 | task_pri_57 | task_pri_63 |
如上表所示 : 第一列的8位組合成為OSRdyGrp的值,用來表征任務的優先級實在哪個組,置一表示有任務就緒,置0表示無任務就緒
第一行的8bit組成一個OSRdyTbl[0..7]的值,OSRdyTbl[]中有8個值每一個值對應一個組的就緒任務,這個值的8bit又是對應上表的列名稱
例如 我有一個優先級為20的任務准備就緒,並且如上表標識.
那么OSRdyGrp = 0x01;
OSRdyTbl[] = {0x00 , 0x00 , 0x10, 0x00 , 0x00 , 0x00, 0x00 , 0x00};
任務就緒可以通過下面的操作來完成
OSRdyGrp |= OSMapTbl[prio >> 3]; // prio 為一個8位的數據,假設prio為20,那么他的高三位表征了他是低2組中的某一個中斷
OSRdyTbl[prio >> 3] |= OSMapTbl[prio & 0x07]; //prio 為一個8位的數據,假設prio為20,那么他的低三位表征了他是某一組中的第4個中斷
OSMapTbl[] = {0x01 , 0x02 , 0x04 , 0x08 , 0x10 , 0x20 , 0x40, 0x80}; //這個查找表就是要讓它對應到位上
查找就緒表中的最高優先級的任務:
由於要滿足實時系統的實時性,那么查找到最高優先級的任務就不能通過遍歷64個就緒表來確定,應為那樣遍歷的時間是不確定的。
所以重點使用OSRdyGrp 變量來實現。
OSRdyGrp 變量是包含了所有組存在就緒任務的信息的,如果OSRdyGrp_bit0(OSRdyGrp變量的最低位)為1,那么表征在整個就緒表中,第0組出現了已就緒的最高優先級任務,
那么在通過使用第0組中的OSRdyTbl[0]來獲取最高優先級任務並執行。
假設OSRdyGrp = 0x05,
OSMapTbl[] = {0x01 , 0x00 , 0x10, 0x00 , 0x00 , 0x00, 0x00 , 0x00};
此時任務就緒表中就有兩個任務是就緒的,分別是
第0組中的第0個任務 就是 task_pri_0。
第2組中的第4個任務 就是 task_pri_20。
所以會選擇task_pri_0去執行。那么如何讓ucos自動執行並且計算的時間是固定的,那么引入下面一種解碼表:
這張表的目的就是通過變量OSRdyGrp 來確定那個組中存在最高優先級以就緒的任務。
原理如下所示:
OSRdyGrp 變量表征了所有組數存在的就緒任務,OSRdyGrp 為一個8位的變量,總共能夠表征的可能性為255種。
假設OSRdyGrp_bit0 = 1.此時無論OSRdyGrp其他位為何值,最高優先級都應該在第0組才是對的,那么:
OSRdyGrp = 0b0000_0001 //1
OSRdyGrp = 0b0000_0011 //3
OSRdyGrp = 0b0000_0101 //5
OSRdyGrp = 0b0000_0111 //7
......
OSRdyGrp = 0b1111_1111 //255
只要OSRdyGrp的值滿足為奇數,那么此時的最高優先級任務就出現在了第0組中,對應於OSUnMapTbl[]中,所有為奇數的值都是為0的,這就表征了這個OSRdyGrp得值
表征了最高優先級任務出現再第0組中。
假設OSRdyGrp_bit1 = 1並且OSRdyGrp_bit1 = 0.此時無論OSRdyGrp其他位為何值,最高優先級都應該在第1組才是對的,那么:
OSRdyGrp = 0b0000_0010 //2
OSRdyGrp = 0b0000_0110 //6
OSRdyGrp = 0b0000_1010 //10
OSRdyGrp = 0b0000_1110 //15
......
OSRdyGrp = 0b1111_1110 //254
只要OSRdyGrp的值滿足為上述序列,那么此時的最高優先級任務就出現在了第1組中,對應於OSUnMapTbl[]中,所有滿足上述序列的值都是為1的,這就表征了這個OSRdyGrp得值
表征了最高優先級任務出現再第1組中。
以此類推。。。。
所以ucos用來求解最高優先級任務的方法如下所示:
y = OSUnMapTbl[OSRdyGrp]; //首先通過OSUnMapTbl[]表來確定最高優先級任務在哪一個組中
x = OSUnMapTbl[OSRdyTbl[y]]; //通過取得對應組的OSRdyTbl[]中表針的8個任務,再通過OSUnMapTbl[]又獲取到最高優先級的那個任務序號。
prio = (y << 3) + x; //合並對應組合對應個數的任務優先級,得到最終最高優先級任務的序號。
只用了三步,確定了查找時間。巧妙的很
//------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
下文是看到另外一位高手的文章,也轉載過來:地址為:http://www.cnblogs.com/dengxiaojun/p/4322471.html
ucos任務調度原理及任務就緒表
之前我們說到,系統在運行的時候會直接依靠任務的優先級來找到任務的控制塊從而實現任務的調用切換等功能,那么接下來的問題就是,系統是怎么找到並確定某一個特定的最高優先級任務並確定他的優先級的呢
為了解決這個問題,ucos采用了一種比較巧妙地方式,叫做就緒任務表,定義如下
OS_EXT OS_PRIO OSRdyTbl[OS_RDY_TBL_SIZE];
可以見到,就緒任務表的大小為OS_RDY_TBL_SIZE, OS_RDY_TBL_SIZE展開就是
#define OS_RDY_TBL_SIZE ((OS_LOWEST_PRIO) / 8u + 1u)
也就是系統(最低優先級任務/8)+1,我們可以認為當有63個任務的時候,就緒表的數據大小為8,每個元素的大小為
#if OS_LOWEST_PRIO <= 63u
typedef INT8U OS_PRIO;
#else
typedef INT16U OS_PRIO;
#endif
當小於63的時候為八位,大於63為16位,為便於分析,我們假設當前系統任務數量小於63
另外為了解析任務就緒表,還需要知道一個變量
OS_EXT OS_PRIO OSRdyGrp;
該變量是將任務控制表分組的變量
任務控制表的原理為使用數據的位標識特定的優先級的任務reday,假如有63個優先級,那么一共就有8個OSRdyTbl元素,每個元素八位,正好有64個位來存放,同時,則64個位分為八組,每組就是一個字節,對應OSRdyGrp的一個位,這樣,通過OSRdyGrp中的位就能找到相應的reday任務位於哪個數組元素中,通過元素的哪一位為1就能確定哪一位中斷發生,此時,我們就有了兩個值
此時,我們就可以根據優先級找到任務在就緒表中的位置,因為prio最大為63,即二進制00111111,將345位指明變量OSRdyGrp,000b-111b分別代表0-7位 012位指明該數組元素的具體數據位,也是000b-111b分別代表0-7位,就能直接確定當前這個優先級兩個數據中的位置
舉例說明如果某個優先級00001100(12)的任務准備好,那么OSRdyGrp的第一位為1, OSRdyTbl[1]的第4位為1,同樣可以反推.
當遇到多個准備好的任務的時候,因為系統總會選用最高優先級的任務,而最高優先級的任務數值是最小的,所以能迅速選擇出當前最高優先級的任務
下面看代碼,當系統設置某個任務為ready的時候,代碼如下
if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY) {
OSRdyGrp |= ptcb->OSTCBBitY; OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
這里面用到了幾個變量,是存放在任務的tcb中的
OSTCBBitY OSTCBBitX OSTCBY初始化任務的時候被賦值,如下
ptcb->OSTCBY = (INT8U)(prio >> 3u);
ptcb->OSTCBX = (INT8U)(prio & 0x07u);
ptcb->OSTCBBitY = (OS_PRIO)(1uL << ptcb->OSTCBY);
ptcb->OSTCBBitX = (OS_PRIO)(1uL << ptcb->OSTCBX);
可以看出, OSTCBY是針對於OSRdyGrp的變量,他將優先級的345位變成了bcd碼的數據OSTCBBitY, OSTCBX是針對於OSRdyTbl的,將012位解析成為OSTCBBitX,這樣置位的時候只要相互或一下就能將reday設置好,舉例來說,還是剛才的00001100(12),計算出來
OSTCBY = 1 OSTCBBitY = 0b00000010 OSTCBX = 4 OSTCBBitX = 0b00010000
經過下面的運算
OSRdyGrp |= ptcb->OSTCBBitY; OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
和之前我們餓推理是不是一模一樣?滿巧妙地,
設置1的方法有了清零就顯而易見的通過與非實現
pevent->OSEventTbl[y] &= (OS_PRIO)~ptcb->OSTCBBitX; /* Remove task from wait list */
if (pevent->OSEventTbl[y] == 0u) {
pevent->OSEventGrp &= (OS_PRIO)~ptcb->OSTCBBitY;
}
當從reday分組中根據表取出最高優先級的任務優先級的方法為
y = OSUnMapTbl[OSRdyGrp];
OSPrioHighRdy = (INT8U)((y << 3u) + OSUnMapTbl[OSRdyTbl[y]]);
首先獲取分組,使用了一個靜態數組,數據如下
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 */
};
乍一看數組OSUnMapTbl的真實意義不明顯,但是仔細分析就可以看出,這個數組實際上是將一個八位數據中的為1的最低位的位置找出來,所以他的設置規律我們可以看到,凡是下標的二進制碼的第0為為1設置為0,第一位為1設置為2…第七位為1設置為7,大家可以驗證,我驗證一下7,第七位為1而且是最低位的話只有一個數0b10000000(其他位都必須是0),這個時候對應的數組元素應該是7,元素位置0x80,可以正好對上.要分析明白這一段始終要記得一個原則,ucos的優先級數值越低,優先級越高,所以尋找最高優先級必然是從最低位開始找起.
相當於就是說這個數據能夠快速將bcd碼轉換為ascii碼,從而避免的循環,避開了實時操作系統的大忌(運行時間不可測).
在得到兩個數據(一個優先級分組一個分組優先級)之后,將兩個數據相互組合,就能得到系統的最低優先級了.
所以
y = OSUnMapTbl[OSRdyGrp];
OSPrioHighRdy = (INT8U)((y << 3u) + OSUnMapTbl[OSRdyTbl[y]]);
很巧妙地方式.
Ucos的任務創建采用兩個函數
OSTaskCreate
OSTaskCreateExt
之前我們已經說了創建任務先做堆棧初始化,然后做tcb任務控制塊初始化,接下來的工作如下
if (err == OS_ERR_NONE) {
if (OSRunning == OS_TRUE) {
OS_Sched();
}
} else {
檢測系統是否處在運行狀態,在運行狀態就觸發一次任務調度,這次調度的基礎還是在tcb_init中,如下
OSRdyGrp |= ptcb->OSTCBBitY; /* Make task ready to run*/
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
OSTaskCtr++; /* Increment the #tasks counter */
創建任務的時候默認將任務設置為運行狀態,在rdytab中掛好標志,這樣接下來調度器就能根據任務的優先級和當前操作系統狀態來決定是否執行該任務,如果創建任務優先級低於當前正在執行的任務,那么就會等待
使用ext方法創建數據的時候,比普通的創建方法多了幾個參數,這些參數分別是
任務的標示id 任務堆棧棧底指針 任務堆棧容量 附加數據域的指針 操作選項option
這些參數的功能在於運行時可以實時的監控任務
另外,千萬記得很重要的一點,ucos不能在中斷中創建任務
任務的掛起和恢復采用兩個函數
OSTaskSuspend 任務掛起
OSTaskResume 任務恢復
任務掛起的流程如下
檢測要掛起的是不是空閑任務,空閑任務不能掛起
檢測當前掛起任務時掛起其他別的任務還是掛起自身(掛其自身需要設置掛起優先級為0xff OS_PRIO_SELF),接着按照優先級從系統tcb表中獲取需要掛起的任務tcb,,通過ptcb快速的將系統就緒表中的掛起任務的就緒狀態清除,然后引發一次調度,從而實現掛起,同時要設置tcb中的OSTCBStat為OS_STAT_SUSPEND,防止在后面的調度中任務又被恢復,這里面有一個需要判斷掛起的是不是自身的問題,如果掛起的是自身,需要在掛起之后調度一次,從而完成真正的骨氣,而如果不是當前運行的任務本身,則掛起之后是不需要引發調度的,設置好標志即可
知道了掛起流程,恢復流程相反就可以了,首先檢測優先級的合法性,然后獲取要掛起的任務的tcb指針,判斷任務存在,並且任務當前處於被掛起狀態並且等待時間為0,條件全部符合的話,取消任務在控制塊中的掛起記錄,並且調用一次調度器,因為可能恢復的任務優先級比當前優先級高,需要切換
修改任務優先級
OSTaskChangePrio
該函數能實時的修改任務的優先級,主要修改了幾個部分,tcb中的優先級,重新修改了系統的就緒表(優先級和就緒表是有着一一對應關系的),以及tcb中用於快定位的幾個元素.另外,對於與自身相關的時間信號燈都做了操作,最后使用調度器調度
刪除任務
OSTaskDel
OSTaskDelReq
前一個用來任務刪除自身或者除了空閑任務之外的其他任務
后一個用來a任務向b任務請求刪除b任務
第一個函數的流程是首先從系統就緒表中刪除就緒的該優先級任務,然后將刪除任務的ptcb清零(堆棧不用,因為下一次設置堆棧的時候會重新賦值),最后從系統tcb控制鏈表中刪除掉指定任務的tcb,並將這個tcb切換到空閑tcb控制塊鏈表中,最后調用調度器,執行調度
而第二個任務的刪除則不是實時進行的,他只是將tcb中的狀態設置了一下,當被刪除任務被調用的時候,再去執行的刪除過程,代碼如下
ptcb->OSTCBDelReq = OS_ERR_TASK_DEL_REQ;
如果任務自身向自身發送請求刪除的話,系統會返回當前任務請求刪除的狀態
stat = OSTCBCur->OSTCBDelReq;
如果是OS_TASK_DEL_REQ,那么任務應當在合適的時機刪除自身
這兩種情況是為了更好地解決資源管理的問題,有時候刪除的任務被分配了某些資源,但是操作系統刪除任務的時候並沒有釋放這些資源,會造成資源的丟失或者系統的運行故障,所以用請求刪除的方式,何時刪除有任務自己決定,就不會出現這些問題了,而被刪除放應當在程序中調用OSTaskDelReq函數,參數使用OS_PRIO_SELF,獲取其他任務對自身的刪除請求,從而決定某些操作
例子
請求方 while(OSTaskDelReq(44)==OS_TASK_NOT_EXIST){OSTimeDly(1);}循環等待任務刪除
被請求方if(ISTaskDelReq(OS_PRIO_SELF==OS_TASK_DEL_REQ))
{
//釋放系統資源
OSTaskDel(OS_PRIO_SELF);//執行真正的任務刪除
}
查詢任務的信息
OSTaskQuery
用於獲得指定任務的tcb狀態信息,,該函數返回值為IS_NO_ERR則返回信息可用
任務返回
OS_TaskReturn
該函數在使能任務刪除的情況下會先執行任務刪除,刪除自身,之后死循環延時,延時一般運行不到,因為任務刪除的之后系統調度到新任務了.