Understanding uC/OS-II (3)



    -- Task Management









在前面的文章中,曾說過 task 可以是一個無限的迴圈,也可以是在一次執行完畢後被刪除掉。這裏要注意的是,task 號碼並不是被真正的刪除了,而只是 uC/OS-II 不再理會該 task 號碼,所以該 task 不會再運行。task 看起來與任何 C 函式一樣,具有一個傳回型別和一個傳入參數,只是它不曾傳回。task 的傳回型別必須被定義成 void 型。在本篇中所提到的函式可以在 OS_TASK.C 檔案中找到。如前所述, task 必須是以下兩種函式之一:





void YourTask (void *pdata)
{

for (;;) {
/* 使用者程式碼 */
.

呼叫 uC/OS-II 的服務常式之一:
OSMboxPend();
OSQPend();
OSSemPend();
OSTaskDel(OS_PRIO_SELF);
OSTaskSuspend(OS_PRIO_SELF);
OSTimeDly();
OSTimeDlyHMSM();

/* 使用者程式碼 */
.
}
}





void YourTask (void *pdata)
{
/* 使用者程式碼 */
.
.
OSTaskDel(OS_PRIO_SELF);
}

本篇所講的內容包括如何在使用者的應用程式中建立 task、刪除 task、改變 task 的優先權、暫停和恢復 task,以及獲得有關 task 的資訊。


uC/OS-II 可以管理多達 64 個 task (新版可以管理 256 個 task),然而 uC/OS-II 的作者建議從中保留了四個最高優先權和四個最低優先權的 task 以供 uC/OS-II 在未來使用,所以使用者可以使用的只有 56 個 task。task 的優先權越高,反映優先權的值則越低。在目前的 uC/OS-II 版本中,task 的優先權數也可作為 task 的識別字使用。




Creating a Task, OSTaskCreate()


想讓 uC/OS-II 管理使用者的 task,使用者必須要先建立 task。使用者可以透過傳遞 task 地址和其他參數到以下兩個函式之一來建立 task:OSTaskCreate()OSTaskCreateExt()


OSTaskCreate() 與 uC/OS 是向下相容的,OSTaskCreateExt() 是 OSTaskCreate() 的擴展版本,提供了一些附加的功能。用兩個函式中的任何一個都可以建立 task。task 可以在多工排程開始前建立,也可以在其他 task 的執行過程中被建立。在開始多工排程 (即呼叫 OSStart()) 前,使用者必須建立至少一個 task。task 不能由 ISR 來建立。


OSTaskCreate() 的程式碼如程式清單 L4.1 所述。從中可以知道,OSTaskCreate() 需要四個參數:task 是 task 程式碼的指標, pdata 是當 task 開始執行時傳遞給 task 的參數的指標,ptos 是分配給 task 的堆疊的頂端指標 (參看 task 堆疊),prio 是分配給 task 的優先權。








程式清單 L4.1 OSTaskCreate()



INT8U OSTaskCreate (void (*task)(void *pd), void *pdata, OS_STK *ptos, INT8U prio)
{
void *psp;
INT8U err;

if (prio > OS_LOWEST_PRIO) { (1)
return (OS_PRIO_INVALID);
}

OS_ENTER_CRITICAL();
if (OSTCBPrioTbl[prio] == (OS_TCB *) 0) { (2)
OSTCBPrioTbl[prio] = (OS_TCB *) 1; (3)
OS_EXIT_CRITICAL(); (4)
psp = (void *)OSTaskStkInit(task, pdata, ptos, 0); (5)
err = OSTCBInit(prio, psp, (void *)0, 0, 0, (void *)0, 0); (6)

if (err == OS_NO_ERR) { (7)
OS_ENTER_CRITICAL();
OSTaskCtr++; (8)
OSTaskCreateHook(OSTCBPrioTbl[prio]); (9)
OS_EXIT_CRITICAL();
if (OSRunning) { (10)
OSSched(); (11)
}
} else {
OS_ENTER_CRITICAL();
OSTCBPrioTbl[prio] = (OS_TCB *) 0; (12)
OS_EXIT_CRITICAL();
}
return (err);

} else {
OS_EXIT_CRITICAL();
return (OS_PRIO_EXIST);
}
}

OSTaskCreate() 一開始先檢測分配給 task 的優先權是否有效 [L4.1(1)]。task 的優先權必須在 0 到 OS_LOWEST_PRIO 之間。接著,OSTaskCreate() 要確保在規定的優先權上還沒有建立 task [L4.1(2)]。在使用 uC/OS-II 時,每個 task 都有特定的優先權。如果某個優先權是未使用的,uC/OS-II 透過放置一個non-NULL指標在 OSTCBPrioTbl[] 中來保留該優先權 [L4.1(3)]。這就使得 OSTaskCreate() 在設置 task 資料結構的其他部分時能重新允許中斷 [L4.1(4)]。


然後,OSTaskCreate() 呼叫 OSTaskStkInit() [L4.1(5)],它負責建立 task 的堆疊。該函式是與處理器的硬體有關的函式,可以在 OS_CPU_C.C 檔案中找到。有關實現 OSTaskStkInit() 的細節可參看 Porting uC/OS-II。如果已經有人在你用的處理器上成功地移植了 uC/OS-II,而你又得到了他的程式碼,就不必考慮該函式的實現細節了。OSTaskStkInit() 函式傳回新的堆疊頂端 (psp),並被保存在 task 的 OS_TCB 中。注意使用者得將傳遞給 OSTaskStkInit() 函式的第四個參數 opt 設為 0,因為 OSTaskCreate() 與 OSTaskCreateExt() 不同,它不支援使用者為 task 的建立過程設置不同的選項,所以沒有任何選項可以透過 opt 參數傳遞給 OSTaskStkInit()。


uC/OS-II 支援的處理器的堆疊既可以從上 (高位址) 往下 (低地址) 遞減也可以從下往上遞增。使用者在呼叫 OSTaskCreate() 的時候必須知道堆疊是遞增的還是遞減的 (參看所用處理器的 OS_CPU.H 中的 OS_STACK_GROWTH),因為使用者必須得把堆疊的頂端傳遞給 OSTaskCreate(),而頂端可能是堆疊的最高位址 (堆疊從上往下遞減),也可能是最低位址 (堆疊從下往上長)。


一旦 OSTaskStkInit() 函式完成了建立 task 的堆疊,OSTaskCreate() 就呼叫 OSTCBInit() [L4.1(6)],從未使用的 OS_TCB 堆中獲得並初始化一個 OS_TCB。OSTCBInit() 的程式碼如程式清單 L4.2 所示,它存在於 OS_CORE.C 檔案中而不是 OS_TASK.C 檔案中。 OSTCBInit() 函式首先從 OS_TCB 緩衝區中獲得一個 OS_TCB [L4.2(1)],如果 OS_TCB 堆中有未使用的 OS_TCB [L4.2(2)],就將它初始化 [L4.2(3)]。注意一旦 OS_TCB 被分配,該 task 的創建者就已經完全擁有它了,即使這時 kernel 又建立了其他的 task,這些新 task 也已經不可能對已分配的 OS_TCB 作任何操作,所以 OSTCBInit() 在這時就可以允許有中斷的,並繼續初始化 OS_TCB 的資料單元。








程式清單 L4.2 OSTCBInit()



INT8U OSTCBInit (INT8U          prio,
OS_STK *ptos,
OS_STK *pbos,
INT16U id,
INT16U stk_size,
void *pext,
INT16U opt)
{
OS_TCB *ptcb;


OS_ENTER_CRITICAL();
ptcb = OSTCBFreeList; (1)
if (ptcb != (OS_TCB *)0) { (2)

OSTCBFreeList = ptcb->OSTCBNext;
OS_EXIT_CRITICAL();
ptcb->OSTCBStkPtr = ptos; (3)
ptcb->OSTCBPrio = (INT8U) prio;
ptcb->OSTCBStat = OS_STAT_RDY;
ptcb->OSTCBDly = 0;

#if OS_TASK_CREATE_EXT_EN
ptcb->OSTCBExtPtr = pext;
ptcb->OSTCBStkSize = stk_size;
ptcb->OSTCBStkBottom = pbos;
ptcb->OSTCBOpt = opt;
ptcb->OSTCBId = id;
#else
pext = pext;
stk_size = stk_size;
pbos = pbos;
opt = opt;
id = id;
#endif

#if OS_TASK_DEL_EN
ptcb->OSTCBDelReq = OS_NO_ERR;
#endif

ptcb->OSTCBY = prio >> 3;
ptcb->OSTCBBitY = OSMapTbl[ptcb->OSTCBY];
ptcb->OSTCBX = prio & 0x07;
ptcb->OSTCBBitX = OSMapTbl[ptcb->OSTCBX];

#if OS_MBOX_EN || (OS_Q_EN && (OS_MAX_QS >= 2)) || OS_SEM_EN
ptcb->OSTCBEventPtr = (OS_EVENT *) 0;
#endif

#if OS_MBOX_EN || (OS_Q_EN && (OS_MAX_QS >= 2))
ptcb->OSTCBMsg = (void *)0;
#endif

OS_ENTER_CRITICAL(); (4)
OSTCBPrioTbl[prio] = ptcb; (5)
ptcb->OSTCBNext = OSTCBList;
ptcb->OSTCBPrev = (OS_TCB *) 0;
if (OSTCBList != (OS_TCB *) 0) {
OSTCBList->OSTCBPrev = ptcb;
}

OSTCBList = ptcb;
OSRdyGrp |= ptcb->OSTCBBitY; (6)
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
OS_EXIT_CRITICAL();
return (OS_NO_ERR); (7)

} else {
OS_EXIT_CRITICAL();
return (OS_NO_MORE_TCB);
}
}

當 OSTCBInit() 需要將 OS_TCB 插入到已建立 task 的 OS_TCB 的雙向 link list 中時 [L4.2(5)],它會去禁止中斷 [L4.2(4)]。該雙向 link list 開始於 OSTCBList,而一個新 task 的 OS_TCB 常常被插入到 link list 的表頭。最後,該 task 處於就緒狀態 [L4.2(6)],並且 OSTCBInit() 向它的呼叫者 [OSTaskCreate()] 傳回一個傳回碼表明 OS_TCB 已經被分配和初始化了 [L4.2(7)]。


現在,我們繼續討論 OSTaskCreate() (程式清單 L4.1) 函式了。從 OSTCBInit() 傳回後,OSTaskCreate() 要檢驗傳回碼 [L4.1(7)],如果成功,就增加 OSTaskCtr [L4.1(8)],OSTaskCtr 用於保存產生的 task 數目。如果 OSTCBInit() 傳回失敗,就設置 OSTCBPrioTbl[prio] 的入口為 0 [L4.1(12)] 以放棄該 task 的優先權。然後,OSTaskCreate() 呼叫 OSTaskCreateHook() [L4.1(9)], OSTaskCreateHook() 是使用者自己定義的函式,用來擴展 OSTaskCreate() 的功能。例如,使用者可以透過 OSTaskCreateHook() 函式來初始化和存儲浮點暫存器、MMU 暫存器的內容,或者其他與 task 相關的內容。一般情況下,使用者可以在記憶體中存儲一些針對使用者的應用程式的附加資訊。OSTaskCreateHook() 既可以在 OS_CPU_C.C 中定義 (如果 OS_CPU_HOOKS_EN 置 1),也可以在其他地方定義。注意,OSTaskCreate() 在呼叫 OSTaskCreateHook() 時,中斷是關掉的,所以使用者應該使 OSTaskCreateHook() 函式中的程式碼應儘量簡化,因為這將直接影響中斷的回應時間。OSTaskCreateHook() 在被呼叫時會收到指向 task 被建立時的 OS_TCB 的指標。這意味著該函式可以存取 OS_TCB 資料結構中的所有成員。


如果 OSTaskCreate() 函式是在某個 task 的執行過程中被呼叫 (即 OSRunning 設為 "TRUE" [L4.1(10)]),則 task 排程函式會被呼叫 [L4.1(11)] 來判斷是否新建立的 task 比原來的 task 有更高的優先權。如果新 task 的優先權更高,kernel 會進行一次從舊 task 到新 task 的 task 切換。如果在多工排程開始之前 (即使用者還沒有呼叫 OSStart()),新 task 就已經建立了,則 task 排程函式不會被呼叫。




Creating a Task, OSTaskCreateExt()


用 OSTaskCreateExt() 函式來建立 task 會更加靈活,但會增加一些額外的開銷。OSTaskCreateExt() 函式的程式碼如程式清單 L4.3 所示。


我們可以看到 OSTaskCreateExt() 需要九個參數!前四個參數 (task,pdata,ptos和 prio) 與 OSTaskCreate() 的四個參數完全相同,連先後順序都一樣。這樣做的目的是為了使使用者能夠更容易地將使用者的程式從 OSTaskCreate() 移植到 OSTaskCreateExt() 上去。


id 參數為要建立的 task 創建一個特殊的識別字。該參數在 uC/OS 以後的升級版本中可能會用到,但目前在 uC/OS-II 中還未使用。這個識別字可以擴展 uC/OS-II 功能,使它可以執行的 task 數超過目前的 64 個。但在這裏,使用者只要簡單地將 task 的 id 設置成與 task 的優先權一樣的值就可以了。


pbos 是指向 task 的堆疊底端的指標,用於堆疊的檢驗。


stk_size 用於指定堆疊成員數目的容量。也就是說,如果堆疊的入口寬度為 4 位元組寬,那麼 stk_size 為 10000 是指堆疊有 40000 個位元組。該參數與 pbos 一樣,也用於堆疊的檢驗。


pext 是指向使用者附加的資料欄的指標,用來擴展 task 的 OS_TCB。例如,使用者可以為每個 task 增加一個名字,或是在 task 切換過程中將浮點暫存器的內容儲存到這個附加資料欄中,等等。


opt 用於設定 OSTaskCreateExt() 的選項,指定是否允許堆疊檢驗,是否將堆疊清零,task 是否要進行浮點操作等等。 uCOS_II.H 檔案中有一個所有可能選項 (OS_TASK_OPT_STK_CHK,OS_TASK_OPT_STK_CLR 和 OS_TASK_OPT_SAVE_FP) 的常數表。每個選項佔有 opt 的一位元,並透過該位元的置位來選定 (使用者在使用時只需要將上述的 OS_TASK_OPT_??? 選項常數進行位元 OR 操作就可以了)。








程式清單 L4.3 OSTaskCreateExt()



INT8U OSTaskCreateExt (void     (*task)(void *pd),
void *pdata,
OS_STK *ptos,
INT8U prio,
INT16U id,
OS_STK *pbos,
INT32U stk_size,
void *pext,
INT16U opt)
{
void *psp;
INT8U err;
INT16U i;
OS_STK *pfill;


if (prio > OS_LOWEST_PRIO) { (1)
return (OS_PRIO_INVALID);
}

OS_ENTER_CRITICAL();
if (OSTCBPrioTbl[prio] == (OS_TCB *)0) { (2)
OSTCBPrioTbl[prio] = (OS_TCB *)1; (3)
OS_EXIT_CRITICAL(); (4)

if (opt & OS_TASK_OPT_STK_CHK) { (5)
if (opt & OS_TASK_OPT_STK_CLR) {
Pfill = pbos;
for (i = 0; i < stk_size; i++) {
#if OS_STK_GROWTH == 1
*pfill++ = (OS_STK) 0;
#else
*pfill-- = (OS_STK) 0;
#endif
}
}
}
psp = (void *) OSTaskStkInit(task, pdata, ptos, opt); (6)
err = OSTCBInit(prio, psp, pbos, id, stk_size, pext, opt); (7)
if (err == OS_NO_ERR) { (8)
OS_ENTER_CRITICAL;
OSTaskCtr++; (9)
OSTaskCreateHook(OSTCBPrioTbl[prio]); (10)
OS_EXIT_CRITICAL();
if (OSRunning) { (11)
OSSched(); (12)
}
} else {
OS_ENTER_CRITICAL();
OSTCBPrioTbl[prio] = (OS_TCB *) 0; (13)
OS_EXIT_CRITICAL();
}
return (err);

} else {
OS_EXIT_CRITICAL();
return (OS_PRIO_EXIST);
}
}

OSTaskCreateExt() 一開始先檢測分配給 task 的優先權是否為有效 [L4.3(1)]。
task 的優先權必須在 0 到 OS_LOWEST_PRIO 之間。接著,OSTaskCreateExt() 要確保在規定的優先權上還沒有建立 task [L4.3(2)]。
在使用 uC/OS-II 時,每個 task 都有特定的優先權。如果某個優先權是未使用的,uC/OS-II 透過放置一個 non-NULL 的指標在 OSTCBPrioTbl[] 中來保留該優先權 [L4.3(3)]。
這就使得 OSTaskCreateExt() 在設置 task 資料結構的其他部分時能重新允許中斷 [L4.3(4)]。
為了對 task 的堆疊進行檢驗 [參看 Task Stacks,OSTaskStkChk()],使用者必須在 opt 參數中設置 OS_TASK_OPT_STK_CHK 旗標。堆疊檢驗還要求在 task 建立時堆疊內存儲的內容都是 0 (即堆疊已被清零)。為了在 task 建立的時候將堆疊清零,需要在 opt 參數中設置 OS_TASK_OPT_STK_CLR。當以上兩個旗標都被設置好後,OSTaskCreateExt() 才能將堆疊清零 [L4.3(5)]。


接著,OSTaskCreateExt() 呼叫 OSTaskStkInit() [L4.3(6)],它負責建立 task 的堆疊。該函式是與處理器的硬體有關的函式,可以在 OS_CPU_C.C 檔案中找到。有關實現 OSTaskStkInit() 的細節可參看 Porting uC/OS-II。如果已經有人在你用的處理器上成功地移植了 uC/OS-II,而你又得到了他的程式碼,就不必考慮該函式的實現細節了。OSTaskStkInit() 函式傳回新的堆疊頂端 (psp),並被保存在 task 的 OS_TCB 中。


uC/OS-II 支援的處理器的堆疊既可以從上 (高位址) 往下 (低地址) 遞減也可以從下往上遞增 (看 Task Stacks)。使用者在呼叫 OSTaskCreateExt() 的時候必須知道堆疊是遞增的還是遞減的 (參看使用者所用處理器的 OS_CPU.H 中的 OS_STACK_GROWTH),因為使用者必須得把堆疊的頂端傳遞給 OSTaskCreateExt(),而頂端可能是堆疊的最低位址 (當OS_STK_GROWTH 為 0 時),也可能是最高地址 (當 OS_STK_GROWTH 為 1 時)。


一旦 OSTaskStkInit() 函式完成了建立堆疊的 task,OSTaskCreateExt() 就呼叫 OSTCBInit() [L4.3(7)],從未使用的 OS_TCB 緩衝區中獲得並初始化一個 OS_TCB。OSTCBInit() 的程式碼在稍早的 OSTaskCreate() 中有討論過,從 OSTCBInit() 傳回後, OSTaskCreateExt() 要檢驗傳回碼 [L4.3(8)],如果成功,就增加 OSTaskCtr [L4.3(9)],OSTaskCtr 用於保存產生的 task 數目。如果 OSTCBInit() 傳回失敗,就設置 OSTCBPrioTbl[prio] 的入口為 0 [L4.3(13)] 以放棄對該 task 優先權的佔用。然後, OSTaskCreateExt() 呼叫 OSTaskCreateHook() [L4.3(10)],OSTaskCreateHook() 是使用者自己定義的函式,用來擴展 OSTaskCreateExt() 的功能。 OSTaskCreateHook() 可以在 OS_CPU_C.C 中定義 (如果 OS_CPU_HOOKS_EN 設為 1),也可以在其他地方定義 (如果 OS_CPU_HOOKS_EN 設為 0)。注意,OSTaskCreateExt() 在呼叫 OSTaskCreateHook() 時,中斷是關掉的,所以使用者應該使 OSTaskCreateHook() 函式中的程式碼儘量簡化,因為這將直接影響中斷的回應時間。OSTaskCreateHook() 被呼叫時會收到指向 task 建立時的 OS_TCB 的指標。這意味著該函式可以存取 OS_TCB 資料結構中的所有成員。


如果 OSTaskCreateExt() 函式是在某個 task 的執行過程中被呼叫的 (即 OSRunning 置為 "TURE" [L4.3(11)]),以 task 排程函式會被呼叫 [L4.3(12)] 來判斷是否新建立的 task 比原來的 task 有更高的優先權。如果新 task 的優先權更高,kernel 會進行一次從舊 task 到新 task 的 task switch。如果在多工排程開始之前 (即使用者還沒有呼叫 OSStart()),新 task 就已經建立了,則 task 排程函式不會被呼叫。




Task Stacks


每個 task 都有自己的堆疊空間。堆疊必須宣告為 OS_STK 型別,並且由連續的記憶體空間組成。 使用者可以靜態分配堆疊空間 (在編譯的時候分配) 也可以動態地分配堆疊空間 (在運行的時候分配)。靜態堆疊宣告如程式清單 L4.4 和 L4.5 所示,這兩種宣告應放置在函式的外面。








程式清單 L4.4 Static Stack



	static OS_STK  MyTaskStack[stack_size];








程式清單 L4.5 Static Stack



	OS_STK  MyTaskStack[stack_size];

使用者可以用 C 編譯器提供的 malloc() 函式來動態地分配堆疊空間,如程式清單 L4.6 所示。在動態分配中,使用者要時常注意記憶體破碎 (fragmentation) 的問題。特別是當使用者反復地建立和刪除 task 時,記憶體中可能會出現大量的記憶體碎片,導致沒有足夠大且連續的一塊記憶體區來做為 task 堆疊,這時 malloc() 便無法成功地為 task 分配堆疊空間。








程式清單 L4.6 Using 'malloc()' to allocate stack space for a task



	OS_STK *pstk;

pstk = (OS_STK *)malloc(stack_size);
if (pstk != (OS_STK *) 0) { /* 確認 malloc() 能得到足夠地記憶體空間 */
.
Create the task;
.
}

圖 F4.1 表示了一塊能被 malloc() 動態分配的 3K 位元組的記憶體堆 [F4.1(1)]。為了討論問題方便,假定使用者要建立三個 task (task A,B 和 C),每個 task 需要 1K 位元組的空間。設第一個 1K 位元組給 task A,第二個 1K 位元組給 task B,第三個 1K 位元組給 task C [F4.1(2)]。然後,使用者的應用程式刪除 task A 和 task C,用 free() 函式釋放記憶體到記憶體堆中 [F4.1(3)]。現在,使用者的記憶體堆雖有 2K 位元組的未使用記憶體空間,但它是不連續的,所以使用者不能建立另一個需要 2K 位元組記憶體的 task (即 task D)。如果使用者並不會去刪除 task,使用 malloc() 是非常可行的。



圖 F4.1 記憶體碎片


uC/OS-II 支援的處理器的堆疊既可以從上 (高位址) 往下 (低地址) 長也可以從下往上長。使用者在呼叫 OSTaskCreate() 或 OSTaskCreateExt() 的時候必須知道堆疊是怎樣長的,因為使用者必須得把堆疊的頂端傳遞給以上兩個函式,當 OS_CPU.H 檔案中的 OS_STK_GROWTH 置為 0 時,使用者需要將堆疊的最低記憶體位址傳遞給 task 創建函式,如程式清單 L4.7 所示。








程式清單 L4.7 Stack grows from LOW memory to HIGH memory



OS_STK TaskStack[TASK_STACK_SIZE];

OSTaskCreate(task, pdata, &TaskStack[0], prio);

當 OS_CPU.H 檔案中的 OS_STK_GROWTH 設為 1 時,使用者需要將堆疊的最高記憶體位址傳遞給 task 建立函式,如程式清單 L4.8 所示。








程式清單 L4.8 Stack grows from HIGH memory to LOW memory



OS_STK TaskStack[TASK_STACK_SIZE];

OSTaskCreate(task, pdata, &TaskStack[TASK_STACK_SIZE-1], prio);

這個問題會影響程式的可攜性。如果使用者想將程式從支援往下遞減堆疊的處理器中移植到支援往上遞增堆疊的處理器中的話,使用者得使程式碼同時適應以上兩種情況。在這種特殊情況下,程式清單 L4.7 和 L4.8 可重新寫成如程式清單 L4.9 所示的形式。








程式清單 L4.9 Supporting stacks which grow in either direction.



OS_STK TaskStack[TASK_STACK_SIZE];


#if OS_STK_GROWTH == 0
OSTaskCreate(task, pdata, &TaskStack[0], prio);
#else
OSTaskCreate(task, pdata, &TaskStack[TASK_STACK_SIZE-1], prio);
#endif

task 所需要的堆疊的容量是由應用程式指定的。使用者在指定堆疊大小的時候必須考慮使用者的 task 所呼叫的所有函式的巢狀情況,task 所呼叫的所有函式會分配的局部變數的數目,以及所有可能的巢狀 ISR 的堆疊需求。另外,使用者的堆疊必須能儲存所有的 CPU 的暫存器




Stack Checking, OSTaskStkChk()


有時候決定 task 實際所需的堆疊空間大小是很有必要的。因為這樣使用者就可以避免為 task 分配過多的堆疊空間,從而減少自己的應用程式所需的 RAM (記憶體) 數量。uC/OS-II 提供的 OSTaskStkChk() 函式可以為使用者提供這種資訊。


在圖 F4.2中,在這裡我們先假定堆疊是從上往下遞減的 (即 OS_STK_GROWTH 被設為 1),但以下的討論也同樣適用於從下往上長的堆疊 [F4.2(1)]。uC/OS-II 是透過查看堆疊本身的內容來決定堆疊的方向的。只有 kernel 或是 task 發出堆疊檢驗的命令時,堆疊檢驗才會被執行,它不會自動地去不斷檢驗 task 的堆疊使用情況。在堆疊檢驗時,uC/OS-II 要求在 task 建立的時候堆疊中存儲的必須是 0 值 (即堆疊被清零) [F4.2(2)]。另外,uC/OS-II 還需要知道堆疊底端 (BOS) 的位置和分配給 task 的堆疊的大小 [F4.2(2)]。在 task 建立的時候,BOS 的位置及堆疊的大小這兩個值都儲存在 task 的 OS_TCB 中。


為了使用 uC/OS-II 的堆疊檢驗功能,使用者必須要做以下幾件事情:




  • 在 OS_CFG.H 檔案中設 OS_TASK_CREATE_EXT 為 1。



  • 用 OSTaskCreateExt() 建立 task,並給予 task 比實際需要更多的記憶體空間。



  • 在 OSTaskCreateExt() 中,將參數 opt 設置為 OS_TASK_OPT_STK_CHK + OS_TASK_OPT_STK_CLR。注意如果使用者的 程式啟動程式碼清除了所有的 RAM,並且從未刪除過已建立了的 task,那麼使用者就不必設置選項 OS_TASK_OPT_STK_CLR 了。 這樣就會減少 OSTaskCreateExt() 的執行時間。



  • 將使用者想檢驗的 task 的優先權作為 OSTaskStkChk() 的參數並呼叫之。




圖 F4.2 Stack checking


OSTaskStkChk() 順著堆疊的底端開始計算未使用的堆疊空間大小,具體實現方法是統計儲存值為 0 的連續堆疊入口的數目,直到發現儲存值不為 0 的堆疊入口 [F4.2(5)]。注意堆疊入口的儲存值在進行檢驗時使用的是堆疊的資料型別 (參看 OS_CPU.H 中的 OS_STK)。換句話說,如果堆疊入口有 32 位元寬,對 0 值的比較也是按 32 位元完成的。所用的堆疊的空間大小是指從使用者在 OSTaskCreateExt() 中定義的堆疊大小中減去了儲存值為 0 的連續堆疊入口以後的大小。OSTaskStkChk() 實際上把未使用堆疊的位元組數和已用堆疊的位元組數放置在 OS_STK_DATA 資料結構中 (參看 uCOS_II.H)。注意在某個特定的時間,被檢驗的 task 的堆疊指標可能會指向最初的堆疊頂端 (TOS) 與堆疊最深處之間的任何位置 [F4.2(7)]。每次在呼叫 OSTaskStkChk() 的時候,使用者也可能會因為 task 還沒觸及堆疊的最深處而得到不同的堆疊的未使用空間數。


使用者應該使自己的應用程式運行足夠長的時間,並且經歷最壞的堆疊使用情況,這樣才能得到正確的數。一旦 OSTaskStkChk() 提供給使用者最壞情況下堆疊的需求,使用者就可以重新設置堆疊的最後容量了。為了適應系統以後的升級和擴展,建議使用者應該多分配 10% ~ 25% 的堆疊空間。在堆疊檢驗中,使用者所得到的只是一個大致的堆疊使用情況,並不能說明堆疊使用的全部實際情況。


OSTaskStkChk() 函式的程式碼如程式清單 L4.10 所示。OS_STK_DATA (參看 uCOS_II.H) 資料結構用來保存有關 task 堆疊的資訊。 uC/OS-II 打算用一個資料結構來達到兩個目的。




  • 第一,把 OSTaskStkChk() 當作是查詢型別的函式,並且使所有的查詢函式用同樣的方法傳回,即傳回查詢資料到 某個資料結構中。



  • 第二,在資料結構中傳遞資料使得 uCOS-II 可以在不改變 OSTaskStkChk() 的 API 的條件下為該資料結構增加其他欄 位,從而擴展 OSTaskStkChk() 的功能。現在,OS_STK_DATA 只包含兩個欄位:OSFreeOSUsed



從程式碼中我們可看到,透過指定執行堆疊檢驗的 task 的優先權可以呼叫 OSTaskStkChk()。如果使用者指定 OS_PRIO_SELF [L4.10(1)],那麼就表明使用者想知道當前 task 的堆疊資訊。當然,前提是 task 已經存在 [L4.10(2)]。要執行堆疊檢驗,使用者必須已用 OSTaskCreateExt() 建立了 task 並且已經由 opt 傳遞了選項 OS_TASK_OPT_CHK [L4.10(3)]。如果所有的條件都滿足了, OSTaskStkChk() 就會像前面描述的那樣從堆疊底端開始統計堆疊的未使用空間 [L4.10(4)]。最後,儲存在 OS_STK_DATA 中的資訊就被確定下來了 [L4.10(5)]。注意函式所確定的是堆疊的實際未使用位元組數和已被佔用的位元組數,而不是堆疊的總位元組數。當然,堆疊的實際大小 (用位元組表示) 就是該兩項之和。








程式清單 L4.10 Stack checking function



INT8U OSTaskStkChk (INT8U prio, OS_STK_DATA *pdata)
{
OS_TCB *ptcb;
OS_STK *pchk;
INT32U free;
INT32U size;


pdata->OSFree = 0;
pdata->OSUsed = 0;
if (prio > OS_LOWEST_PRIO && prio != OS_PRIO_SELF) {
return (OS_PRIO_INVALID);
}

OS_ENTER_CRITICAL();
if (prio == OS_PRIO_SELF) { (1)
prio = OSTCBCur->OSTCBPrio;
}

ptcb = OSTCBPrioTbl[prio];
if (ptcb == (OS_TCB *) 0) { (2)
OS_EXIT_CRITICAL();
return (OS_TASK_NOT_EXIST);
}

if ((ptcb->OSTCBOpt & OS_TASK_OPT_STK_CHK) == 0) { (3)
OS_EXIT_CRITICAL();
return (OS_TASK_OPT_ERR);
}

free = 0; (4)
size = ptcb->OSTCBStkSize;
pchk = ptcb->OSTCBStkBottom;
OS_EXIT_CRITICAL();

#if OS_STK_GROWTH == 1
while (*pchk++ == 0) {
free++;
}
#else
while (*pchk-- == 0) {
free++;
}
#endif

pdata->OSFree = free * sizeof(OS_STK); (5)
pdata->OSUsed = (size - free) * sizeof(OS_STK);
return (OS_NO_ERR);
}



Deleting a Task, OSTaskDel()


有時候刪除 task 是很有必要的。刪除 task,是說 task 將傳回並處於休眠狀態 (參看 Task States),並不是說 task 的程式碼被刪除了,只是 task 的程式碼不再被 uC/OS-II 呼叫。透過呼叫 OSTaskDel() 就可以完成刪除 task 的功能 (如程式清單 L4.11 所示)。 OSTaskDel() 一開始是確保使用者所要刪除的 task 並非是 idle task,因為刪除 idle task 是不允許的 [L4.11(1)]。不過,使用者可以刪除 statistic task [L4.11(2)]。接著,OSTaskDel() 還必須確定使用者不是在 ISR 中去試圖刪除某一個 task,因為這也是不被允許的 [L4.11(3)]。呼叫此函式的 task 可以透過指定 OS_PRIO_SELF 參數來刪除自己 [L4.11(4)]。接下來 OSTaskDel() 會保證被刪除的 task 是確實存在的 [L4.11(3)]。如果指定的參數是 OS_PRIO_SELF 的話,這一判斷過程 (task 是否存在) 自然是可以透過的,但 uC/OS-II 不準備為這種情況單獨寫一段程式碼,因為這樣只會增加程式碼並延長程式的執行時間。








程式清單 L4.11 Task Delete



INT8U OSTaskDel (INT8U prio)
{
OS_TCB *ptcb;
OS_EVENT *pevent;


if (prio == OS_IDLE_PRIO) { (1)
return (OS_TASK_DEL_IDLE);
}
if (prio >= OS_LOWEST_PRIO && prio != OS_PRIO_SELF) { (2)
return (OS_PRIO_INVALID);
}

OS_ENTER_CRITICAL();
if (OSIntNesting > 0) { (3)
OS_EXIT_CRITICAL();
return (OS_TASK_DEL_ISR);
}
if (prio == OS_PRIO_SELF) { (4)
Prio = OSTCBCur->OSTCBPrio;
}
if ((ptcb = OSTCBPrioTbl[prio]) != (OS_TCB *)0) { (5)
if ((OSRdyTbl[ptcb->OSTCBY] &= ~ptcb->OSTCBBitX) == 0) { (6)
OSRdyGrp &= ~ptcb->OSTCBBitY;
}

if ((pevent = ptcb->OSTCBEventPtr) != (OS_EVENT *)0) { (7)
if ((pevent->OSEventTbl[ptcb->OSTCBY] &= ~ptcb->OSTCBBitX) == 0) {
pevent->OSEventGrp &= ~ptcb->OSTCBBitY;

}

}

Ptcb->OSTCBDly = 0; (8)
Ptcb->OSTCBStat = OS_STAT_RDY; (9)
OSLockNesting++; (10)
OS_EXIT_CRITICAL(); (11)

OSDummy(); (12)

OS_ENTER_CRITICAL();
OSLockNesting--; (13)
OSTaskDelHook(ptcb); (14)
OSTaskCtr--;
OSTCBPrioTbl[prio] = (OS_TCB *)0; (15)

if (ptcb->OSTCBPrev == (OS_TCB *)0) { (16)
ptcb->OSTCBNext->OSTCBPrev = (OS_TCB *) 0;
OSTCBList = ptcb->OSTCBNext;
} else {
ptcb->OSTCBPrev->OSTCBNext = ptcb->OSTCBNext;
ptcb->OSTCBNext->OSTCBPrev = ptcb->OSTCBPrev;
}

ptcb->OSTCBNext = OSTCBFreeList; (17)
OSTCBFreeList = ptcb;

OS_EXIT_CRITICAL();
OSSched(); (18)

return (OS_NO_ERR);

} else {
OS_EXIT_CRITICAL();
return (OS_TASK_DEL_ERR);
}
}

一旦所有條件都滿足了,OS_TCB 就會從所有可能的 uC/OS-II 的資料結構中移除。OSTaskDel() 分兩步完成該移除 task 以減少中斷回應時間。首先,如果 task 處於 ready list 中,它會直接被移除 [L4.11(6)]。如果 task 處於 mailbox、message queue 或 semaphore 的等待表中,它就從自己所處的表中被移除 [L4.11(7)]。接著,OSTaskDel() 將 task 的 clock tick 延遲數清零,以確保自己重新允許中斷的時候,ISR 常式不會使該 task 就緒 [L4.11(8)]。最後,OSTaskDel() 設置 task 的 OSTCBStat 旗標為 OS_STAT_RDY。注意, OSTaskDel() 並不是試圖使 task 處於 READY,而是阻止其他 task 或 ISR 常式讓該 task 重新開始執行 (即避免其他 task 或 ISR 呼叫 OSTaskResume() [L4.11(9)])。這種情況是有可能發生的,因為 OSTaskDel() 會重新打開中斷,而 ISR 可以讓更高優先權的 task 處於 READY,這就可能會使使用者想刪除的 task 重新開始執行。如果不想設置 task 的 OSTCBStat 旗標為 OS_STAT_RDY,就只能清除 OS_STAT_SUSPEND 欄位了 (這樣程式碼可能顯得更清楚,更容易理解一些),但這樣會使得處理時間稍長一些。


要被刪除的 task 不會被其他的 task 或 ISR 設為於就緒狀態,因為該 task 已從就緒 task 表中刪除了,它不是在等待事件的發生,也不是在等待延時期滿,所以不能重新被執行。為了達到刪除 task 的目的,task 被置於休眠狀態。正因為這樣,OSTaskDel() 必須得阻止 task 排程程式 [L4.11(10)] 在刪除過程中切換到其他的 task 中去,因為如果當前的 task 正在被刪除,它不可能被再次排程!接下來,OSTaskDel() 重新允許中斷以減少中斷的回應時間 [L4.11(11)]。這樣,OSTaskDel() 就能處理中斷服務了,但由於它增加了 OSLockNesting,ISR 執行完後會傳回到被中斷 task ,從而繼續 task 的刪除工作。注意 OSTaskDel() 此時還沒有完全完成刪除 task 的工作,因為它還需要從 TCB link list 中解開 OS_TCB,並將 OS_TCB 傳回到未使用 OS_TCB 表中。


另外需要注意的是,uC/OS-II 在呼叫 OS_EXIT_CRITICAL() 函式後,馬上呼叫了 OSDummy() [L4.11(12)],該函式並不會進行任何實質性的工作。這樣做只是因為想確保處理器在中斷允許的情況下至少執行一個指令。對於許多處理器來說,執行中斷允許指令會強制 CPU 禁止中斷直到下個指令結束!Intel 80x86 和 Zilog Z-80 處理器就是如此工作的。開中斷後馬上關中斷就等於從來沒開過中斷,當然這會增加中斷的回應時間。因此呼叫 OSDummy() 是確保在再次禁止中斷之前至少執行了一個呼叫指令和一個傳回指令。當然,使用者可以用巨集定義將 OSDummy() 定義為一個 NOP 指令,這樣呼叫 OSDummy() 就等於執行了一個 NOP 指令,會使 OSTaskDel() 的執行時間稍微縮短一點。但 uC/OS-II 的作者認為這種巨集定義是沒價值的,因為它只會增加移植 uCOS-II 的工作量。


現在,OSTaskDel() 可以繼續執行刪除 task 的操作了。在 OSTaskDel() 重新關中斷後,它透過鎖定巢狀計數器 (OSLockNesting) 減一來允許 task 重新排程 [L4.11(13)]。接著,OSTaskDel() 呼叫使用者自定義的 OSTaskDelHook() 函式 [L4.11(14)],使用者可以在這裏刪除或釋放自定義的 TCB 附加欄位。然後,OSTaskDel() 減少 uCOS-II 的 task 計數器。OSTaskDel() 簡單地將指向被刪除的 task 的 OS_TCB 的指標指向 NULL [L4.11(15)],從而達到將 OS_TCB 從優先權表中移除的目的。再接著,OSTaskDel() 將被刪除的 task 的 OS_TCB 從 OS_TCB 雙向 link list 中移除 [L4.11(16)]。注意,沒有必要檢驗 ptcb->OSTCBNext == 0 的情況,因為 OSTaskDel() 不能刪除 idle task,而 idle task 就處於 link list 的末端 (ptcb->OSTCBNext == 0)。接下來,OS_TCB 傳回到未使用 OS_TCB 表中,並允許其他 task 的建立 [L4.11(17)]。最後,呼叫 task 排程程式來查看在 OSTaskDel() 重新允許中斷的時候 [L4.11(11)],ISR 是否曾使更高優先權的 task 處於就緒狀態 [L4.11(18)]。




Requesting to delete a task, OSTaskDelReq()


有時候,如果 Task A 擁有記憶體緩衝區或 semaphore 之類的資源,而 Task B 想刪除該 task,這些資源就可能由於沒被釋放而無法使用。在這種情況下,使用者可以想法子讓擁有這些資源的 task 在使用完資源後,先釋放資源再刪除自己。使用者可以透過 OSTaskDelReq() 函式來完成該功能。


發出刪除 task 請求的 task (Task B) 和要被刪除的 task (Task A) 都需要呼叫 OSTaskDelReq() 函式。Task B 的程式碼如程式清單 L4.12 所示。Task B 需要決定在怎樣的情況下請求刪除 task [L4.12(1)]。換句話說,使用者的應用程式需要決定在什麼樣的情況下刪除 task。如果 task 需要被刪除,可以透過傳遞被刪除 task 的優先權來呼叫 OSTaskDelReq() [L4.12(2)]。如果要被刪除的 task 不存在 (即 task 已被刪除或是還沒被建立),OSTaskDelReq() 傳回 OS_TASK_NOT_EXIST。如果 OSTaskDelReq() 的傳回值為 OS_NO_ERR,則表示請求已被接受但 task 還沒被刪除。使用者可能希望 task B 等到 task A 刪除了自己以後才繼續進行下面的工作,這時使用者可以像程式清單 L4.12 一樣,透過讓 Task B 延時一定時間來達到這個目的 [L4.12(3)]。程式清單 L4.12 中延時了一個時鐘節拍。如果需要,使用者可以延時得更長一些。當 Task A 完全刪除自己後,[L4.12(2)] 中的傳回值成為 0S_TASK_NOT_EXIST,此時迴圈結束 [L4.12(4)]。








程式清單 L4.12 Requesting a task to delete itself (Task B)



void RequestorTask (void *pdata)
{
INT8U err;


pdata = pdata;
for (;;) {
/* 應用程式碼 */
.

if ('TaskToBeDeleted()' 需要被刪除) { (1)
while (OSTaskDelReq(TASK_TO_DEL_PRIO) != OS_TASK_NOT_EXIST){ (2)
OSTimeDly(1); (3)
}
}

/* 應用程式碼 */ (4)
.

}
}

需要刪除自己的 task (Task A) 的程式碼如程式清單 L4.13 所示。在 OS_TCB 中存有一個旗標,task 透過查詢這個旗標的值來確認自己是否需要被刪除。這個旗標的值是透過呼叫 OSTaskDelReq(OS_PRIO_SELF) 而得到的。當 OSTaskDelReq() 傳回給呼叫者 OS_TASK_DEL_REQ [L4.13(1)] 時,則表明已經有另外的 task (Task B) 請求該 task (Task A) 被刪除了。在這種情況下,被刪除的 task (Task A) 會釋放它所擁有的所用資源 [L4.13(2)],並且呼叫 OSTaskDel(OS_PRIO_SELF) 來刪除自己 [L4.13(3)]。前面曾提到過, task 的程式碼沒有被真正的刪除,而只是 uC/OS-II 不再理會該 task 程式碼,換句話說,就是 task 的程式碼不會再運行了。但是,使用者可以透過呼叫 OSTaskCreate() 或 OSTaskCreateExt() 函式重新建立該 task。








程式清單 L4.13 Requesting a task to delete itself (Task A)



void TaskToBeDeleted (void *pdata)
{
INT8U err;


pdata = pdata;
for (;;) {
/* 應用程式碼 */
.

If (OSTaskDelReq(OS_PRIO_SELF) == OS_TASK_DEL_REQ) { (1)
釋放所有佔用的資源; (2)

釋放所有動態記憶體;

OSTaskDel(OS_PRIO_SELF); (3)

} else {
/* 應用程式碼 */
.

}
}
}

OSTaskDelReq() 的程式碼如程式清單 L4.14 所示。通常 OSTaskDelReq() 需要檢查臨界條件。首先,如果正在刪除的 task 是 idle task, OSTaskDelReq() 會傳回錯誤碼 [L4.14(1)]。接著,它要保證呼叫者請求刪除的 task 的優先權是有效的 [L4.14(2)]。如果呼叫者就是被刪除 task 本身,存儲在 OS_TCB 中的旗標將會作為傳回值 [L4.14(3)]。如果使用者用優先權而不是 OS_PRIO_SELF 指定 task,並且 task 是存在的 [L4.14(4)],OSTaskDelReq() 就會設置 task 的內部旗標 [L4.14(5)]。如果 task 不存在,OSTaskDelReq() 則會傳回 OS_TASK_NOT_EXIST,表明 task 可能已經刪除自己了[L4.14(6)]。








程式清單 L4.14 OSTaskDelReq()



INT8U OSTaskDelReq (INT8U prio)
{
BOOLEAN stat;
INT8U err;
OS_TCB *ptcb;


if (prio == OS_IDLE_PRIO) { (1)
return (OS_TASK_DEL_IDLE);
}
if (prio >= OS_LOWEST_PRIO && prio != OS_PRIO_SELF) { (2)
return (OS_PRIO_INVALID);
}

if (prio == OS_PRIO_SELF) { (3)
OS_ENTER_CRITICAL();
stat = OSTCBCur->OSTCBDelReq;
OS_EXIT_CRITICAL();
return (stat);

} else {
OS_ENTER_CRITICAL();
if ((ptcb = OSTCBPrioTbl[prio]) != (OS_TCB *)0) { (4)
ptcb->OSTCBDelReq = OS_TASK_DEL_REQ; (5)
err = OS_NO_ERR;

} else {
err = OS_TASK_NOT_EXIST; (6)
}

OS_EXIT_CRITICAL();
return (err);
}
}



Changing a Task's Priority, OSTaskChangePrio()


在使用者建立 task 的時候會分配給 task 一個優先權。在程式運行期間,使用者可以透過呼叫 OSTaskChangePrio() 來改變 task 的優先權。換句話說,就是 uC/OS-II 允許使用者動態的改變 task 的優先權。而非 kernel 自行改變 task 的優先權。


OSTaskChangePrio() 的程式碼如程式清單 L4.15 所示。使用者不能改變 idle task 的優先權 [L4.15(1)],但使用者可以改變呼叫本函式的 task 或者其他 task 的優先權。為了改變呼叫本函式的 task 的優先權,使用者可以指定該 task 當前的優先權或 OS_PRIO_SELF, OSTaskChangePrio() 會決定該 task 的優先權。使用者還必須指定 task 的新優先權值。因為 uC/OS-II 不允許多個 task 具有相同的優先權,所以 OSTaskChangePrio() 需要檢驗新優先權是否是合法的 (即不存在具有新優先權的 task) [L4.15(2)]。如果新優先權是合法的,uC/OS-II 透過將某些東西儲存到 OSTCBPrioTbl[newprio] 中來保留這個優先權 [L4.15(3)]。如此就使得 OSTaskChangePrio() 可以重新允許中斷,因為此時其他 task 已經不可能建立擁有該優先權的 task,也不能透過指定相同的新優先權來呼叫 OSTaskChangePrio()。接下來 OSTaskChangePrio() 可以預先計算新優先權 task 的 OS_TCB 中的某些值 [L4.15(4)]。而這些值用來將 task 放入 ready list 或從該表中移除 (參看 Ready List)。


接著,OSTaskChangePrio() 檢驗目前的 task 是否想改變它的優先權 [L4.15(5)]。然後,OSTaskChangePrio() 檢查想要改變優先權的 task 是否存在 [L4.15(6)]。很明顯,如果要改變優先權的 task 就是當前 task,這個測試就會成功。但是,如果 OSTaskChangePrio() 想要改變優先權的 task 不存在,它必須將保留的新優先權放回到優先權表 OSTCBPrioTbl[] 中 [L4.15(17)],並傳回給呼叫者一個錯誤碼。


現在,OSTaskChangePrio() 可以透過插入 NULL 指標將指向當前 task 的 OS_TCB 的指標從優先權表中移除了 [L4.15(7)]。這就使得當前 task 的舊的優先權可以重新使用了。接著,我們檢驗一下 OSTaskChangePrio() 想要改變優先權的 task 是否就緒 [L4.15(8)]。如果該 task 處於就緒狀態,它必須在當前的優先權下從就緒表中移除 [L4.15(9)],然後在新的優先權下插入到就緒表中 [L4.15(10)]。這兒需要注意的是,OSTaskChangePrio() 所用的是重新計算的值 [L4.15(4)] 將 task 插入就緒表中的。


如果 task 已經就緒,它可能會正在等待一個信號量、一封郵件或是一個消息佇列。如果 OSTCBEventPtr non-NULL (不等於NULL) [L4.15(8)], OSTaskChangePrio() 就會知道 task 正在等待以上的某件事。如果 task 在等待某一事件的發生,OSTaskChangePrio() 必須將 task 從事件控制塊 (參看 Event Control Block) 的等待佇列 (在舊的優先權下) 中移除。並在新的優先權下將事件插入到等待佇列中 [L4.15(12)]。 task 也有可能正在等待延時的期滿或是被暫停。在這些情況下,從 L4.15(8) 到 L4.15(12) 這幾行可以略過。


接著,OSTaskChangePrio() 將指向 task 的 OS_TCB 的指標存到 OSTCBPrioTbl[] 中 [L4.15(13)]。新的優先權被保存在 OS_TCB 中 [L4.15(14)],重新計算的值也被保存在 OS_TCB 中 [L4.15(15)]。OSTaskChangePrio() 完成了關鍵性的步驟後,在新的優先權高於舊的優先權或新的優先權高於呼叫本函式的 task 的優先權情況下,task 排程程式就會被呼叫 [L4.15(16)]。








程式清單 L4.15 OSTaskChangePrio()



INT8U OSTaskChangePrio (INT8U oldprio, INT8U newprio)
{
OS_TCB *ptcb;
OS_EVENT *pevent;
INT8U x;
INT8U y;
INT8U bitx;
INT8U bity;


if ((oldprio >= OS_LOWEST_PRIO && oldprio != OS_PRIO_SELF) ||
(newprio >= OS_LOWEST_PRIO)) { (1)
return (OS_PRIO_INVALID);
}

OS_ENTER_CRITICAL();
if (OSTCBPrioTbl[newprio] != (OS_TCB *) 0) { (2)
OS_EXIT_CRITICAL();
return (OS_PRIO_EXIST);

} else {
OSTCBPrioTbl[newprio] = (OS_TCB *) 1; (3)
OS_EXIT_CRITICAL();
y = newprio >> 3; (4)
bity = OSMapTbl[y];
x = newprio & 0x07;
bitx = OSMapTbl[x];
OS_ENTER_CRITICAL();

if (oldprio == OS_PRIO_SELF) { (5)
oldprio = OSTCBCur->OSTCBPrio;
}

if ((ptcb = OSTCBPrioTbl[oldprio]) != (OS_TCB *) 0) { (6)
OSTCBPrioTbl[oldprio] = (OS_TCB *) 0; (7)

if (OSRdyTbl[ptcb->OSTCBY] & ptcb->OSTCBBitX) { (8)
if ((OSRdyTbl[ptcb->OSTCBY] &= ~ptcb->OSTCBBitX) == 0) { (9)
OSRdyGrp &= ~ptcb->OSTCBBitY;

}
OSRdyGrp |= bity; (10)
OSRdyTbl[y] |= bitx;
} else {
if ((pevent = ptcb->OSTCBEventPtr) != (OS_EVENT *)0) { (11)
if ((pevent->OSEventTbl[ptcb->OSTCBY] &= ~ptcb->OSTCBBitX) == 0) {
pevent->OSEventGrp &= ~ptcb->OSTCBBitY;
}
pevent->OSEventGrp |= bity; (12)
pevent->OSEventTbl[y] |= bitx;
}
}
OSTCBPrioTbl[newprio] = ptcb; (13)
ptcb->OSTCBPrio = newprio; (14)
ptcb->OSTCBY = y; (15)
ptcb->OSTCBX = x;
ptcb->OSTCBBitY = bity;
ptcb->OSTCBBitX = bitx;
OS_EXIT_CRITICAL();
OSSched(); (16)
return (OS_NO_ERR);

} else {
OSTCBPrioTbl[newprio] = (OS_TCB *) 0; (17)
OS_EXIT_CRITICAL();
return (OS_PRIO_ERR);
}
}
}



Suspending a Task, OSTaskSuspend()


有時候將 task 暫停是很有用的。暫停 task 可透過呼叫 OSTaskSuspend() 函式來完成。被暫停的 task 只能透過呼叫 OSTaskResume() 函式來恢復。task 暫停是一個附加功能。也就是說,如果 task 在被暫停的同時也在等待延時的期滿,那麼,暫停操作需要被取消,而 task 繼續等待延時期滿,並轉入 READY。task 可以暫停自己或者其他 task。


OSTaskSuspend() 函式的程式碼如程式清單 L4.16 所示。通常 OSTaskSuspend() 需要檢驗臨界條件。首先,OSTaskSuspend() 要確保使用者的應用程式不是在暫停 idle task [L4.16(1)],接著確認使用者指定優先權是有效的 [L4.16(2)]。記住最大的有效的優先權數 (即最低的優先權) 是 OS_LOWEST_PRIO。注意,使用者可以暫停 statistic task。可能使用者已經注意到了,第一個測試 [L4.16(1)] 在 [L4.16(2)] 中被重復了。這樣做是為了能使 uC/OS-II 與 uC/OS 相容。第一個測試能夠被移除並可以節省一點程式處理的時間,但是,這樣做的意義不大,所以 uC/OS-II 的作者決定留下它。


接著,OSTaskSuspend() 檢驗使用者是否透過指定 OS_PRIO_SELF 來暫停呼叫本函式的 task 本身 [L4.16(3)]。使用者也可以透過指定優先權來暫停呼叫本函式的 task [L4.16(4)]。在這兩種情況下,task 排程程式都需要被呼叫。這就是 uC/OS-II 為什麼要定義 局部變數 self 的原因,該變數在適當的情況下會被測試。如果使用者沒有暫停呼叫本函式的 task,OSTaskSuspend() 就沒有必要運行 task 排程程式,因為正在暫停的是較低優先權的 task。


然後,OSTaskSuspend() 檢驗要暫停的 task 是否存在 [L4.16(5)]。如果該 task 存在的話,它就會從 ready list 中移除 [L4.16(6)]。注意要被暫停的 task 有可能沒有在 ready list 中,因為它有可能在等待事件的發生或延時的期滿。在這種情況下,要被暫停的 task 在 OSRdyTbl[] 中對應的欄位已被清除了 (即為 0)。再次清除該欄位,要比先檢驗該欄位是否被清除然後在它沒被清除時去清除它快得多,所以 uC/OS-II 沒有檢驗該欄位而是直接清除它。現在,OSTaskSuspend() 就可以在 task 的 OS_TCB 中設置 OS_STAT_SUSPEND 旗標了,以表示 task 正在被暫停 [L4.16(7)]。最後,OSTaskSuspend() 只有在被暫停的 task 是呼叫本函式的 task 本身的情況下才呼叫 task 排程程式 [L4.16(8)]。








程式清單 L4.16 OSTaskSuspend()



INT8U OSTaskSuspend (INT8U prio)
{
BOOLEAN self;
OS_TCB *ptcb;


if (prio == OS_IDLE_PRIO) { (1)
return (OS_TASK_SUSPEND_IDLE);
}
if (prio >= OS_LOWEST_PRIO && prio != OS_PRIO_SELF) { (2)
return (OS_PRIO_INVALID);
}

OS_ENTER_CRITICAL();

if (prio == OS_PRIO_SELF) { (3)
prio = OSTCBCur->OSTCBPrio;
self = TRUE;

} else if (prio == OSTCBCur->OSTCBPrio) { (4)
self = TRUE;

} else {
self = FALSE;
}

if ((ptcb = OSTCBPrioTbl[prio]) == (OS_TCB *) 0) { (5)
OS_EXIT_CRITICAL();
return (OS_TASK_SUSPEND_PRIO);
} else {
if ((OSRdyTbl[ptcb->OSTCBY] &= ~ptcb->OSTCBBitX) == 0) { (6)
OSRdyGrp &= ~ptcb->OSTCBBitY;
}
ptcb->OSTCBStat |= OS_STAT_SUSPEND; (7)
OS_EXIT_CRITICAL();
if (self == TRUE) { (8)
OSSched();
}
return (OS_NO_ERR);
}
}



Resuming a Task, OSTaskResume()


在上一節中曾提到過,被暫停的 task 只有透過呼叫 OSTaskResume() 才能恢復。OSTaskResume() 函式的程式碼如程式清單 L4.17 所示。因為OSTaskSuspend()不能暫停idle task ,所以必須得確認使用者的應用程式不是在恢復idle task [L4.17(1)]。注意,這個測試也可以確保使用者不是在恢復優先權為OS_PRIO_SELF的 task (OS_PRIO_SELF被定義為0xFF,它總是比OS_LOWEST_PRIO大)。


要恢復的 task 必須是存在的,因為使用者要需要操作它的 task 控制塊OS_TCB[L4.17(2)],並且該 task 必須是被暫停的[L4.17(3)]。OSTaskResume()是透過清除OSTCBStat欄位中的OS_STAT_SUSPEND位來取消暫停的[L4.17(4)]。要使 task 處於就緒狀態,OS_TCBDly欄位必須為0[L4.17(5)],這是因為在OSTCBStat中沒有任何旗標表明 task 正在等待延時的期滿。只有當以上兩個條件都滿足的時候, task 才處於就緒狀態[L4.17(6)]。最後, task 排程程式會檢查被恢復的 task 擁有的優先權是否比呼叫本函式的 task 的優先權高[L4.17(7)]。








程式清單 L4.17 OSTaskResume()



INT8U OSTaskResume (INT8U prio)
{
OS_TCB *ptcb;


If (prio >= OS_LOWEST_PRIO) { (1)
return (OS_PRIO_INVALID);
}

OS_ENTER_CRITICAL();

If ((ptcb = OSTCBPrioTbl[prio]) == (OS_TCB *) 0) { (2)
OS_EXIT_CRITICAL();
return (OS_TASK_RESUME_PRIO);

} else {
if (ptcb->OSTCBStat & OS_STAT_SUSPEND) { (3)
if (((ptcb->OSTCBStat &= ~OS_STAT_SUSPEND) == OS_STAT_RDY) && (4)
(ptcb->OSTCBDly == 0)) { (5)
OSRdyGrp |= ptcb->OSTCBBitY; (6)
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;

OS_EXIT_CRITICAL();
OSSched(); (7)
} else {
OS_EXIT_CRITICAL();
}
return (OS_NO_ERR);
} else {
OS_EXIT_CRITICAL();
return (OS_TASK_NOT_SUSPENDED);
}
}
}



Getting Information about a Task, OSTaskQuery()


使用者的應用程式可以透過呼叫 OSTaskQuery() 來獲得自身或其他應用 task 的資訊。實際上,OSTaskQuery() 獲得的是對應 task 的 OS_TCB 中內容的拷貝。使用者能存取的 OS_TCB 的資料欄的多少決定于使用者的應用程式的配置 (參看 OS_CFG.H)。由於 uC/OS-II 是可裁剪的,它只包括那些使用者的應用程式所要求的功能及屬性。


要呼叫 OSTaskQuery(),如程式清單 L4.18 中所示的那樣,使用者的應用程式必須要為 OS_TCB 分配存儲空間。這個 OS_TCB 與 uC/OS-II 分配的 OS_TCB 是完全不同的資料空間。在呼叫了 OSTaskQuery() 後,這個 OS_TCB 包含了對應 task 的 OS_TCB 的副本。使用者必須十分小心地處理 OS_TCB 中指向其他 OS_TCB 的指標 (即 OSTCBNext 與 OSTCBPrev);使用者不要試圖去改變這些指標!一般來說,本函式只用來瞭解 task 正在幹什麼,則本函式是很有用的調試工具。








程式清單 L4.18 Obtaining information about a task



OS_TCB MyTaskData;

void MyTask (void *pdata)
{
pdata = pdata;

for (;;) {
/* 使用者程式碼 */

err = OSTaskQuery(10, &MyTaskData);

/* Examine error code ... */

/* 使用者程式碼 */

}
}

OSTaskQuery() 的程式碼如程式清單 L4.19 所示。注意,uC/OS-II 允許使用者查詢所有的 task,包括 idle task [L4.19(1)]。使用者尤其需要注意的是不要改變 OSTCBNext 與 OSTCBPrev 的指向。通常,OSTaskQuery() 需要檢驗使用者是否想知道當前 task 的有關資訊 [L4.19(2)] 以及該 task 是否已經建立了 [L4.19(3)]。所有的欄位是透過賦予值的敘述一次性地複製的而不是一個欄位一個欄位地複製的 [L4.19(4)]。這樣複製會比較快一點,因為編譯器大多都能夠產生記憶體拷貝指令。








程式清單 L4.19 OSTaskQuery()



INT8U OSTaskQuery (INT8U prio, OS_TCB *pdata)
{
OS_TCB *ptcb;


if (prio > OS_LOWEST_PRIO && prio != OS_PRIO_SELF) { (1)
return (OS_PRIO_INVALID);
}

OS_ENTER_CRITICAL();

if (prio == OS_PRIO_SELF) { (2)
prio = OSTCBCur->OSTCBPrio;
}
if ((ptcb = OSTCBPrioTbl[prio]) == (OS_TCB *) 0) { (3)
OS_EXIT_CRITICAL();
return (OS_PRIO_ERR);
}
*pdata = *ptcb; (4)
OS_EXIT_CRITICAL();
return (OS_NO_ERR);
}
arrow
arrow
    全站熱搜

    立你斯 發表在 痞客邦 留言(0) 人氣()