之前我們說到,系統在運行的時候會直接依靠任務的優先級來找到任務的控制塊從而實現任務的調用切換等功能,那么接下來的問題就是,系統是怎么找到並確定某一個特定的最高優先級任務並確定他的優先級的呢
為了解決這個問題,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
該函數在使能任務刪除的情況下會先執行任務刪除,刪除自身,之后死循環延時,延時一般運行不到,因為任務刪除的之后系統調度到新任務了.