【UCOSIII】UCOSIII系統內部任務
之前講到UCOSIII預設有5個系統任務:
- 空閒任務:UCOSIII建立的第一個任務,UCOSIII必須建立的任務,此任務有UCOSIII自動建立,不需要使用者手動建立;
- 時鐘節拍任務:此任務也是必須建立的任務;
- 統計任務:可選任務,用來統計CPU使用率和各個任務的堆疊使用量。此任務是可選任務,由巨集OS_CFG_STAT_TASK_EN控制是否使用此任務;
- 定時任務:用來向用戶提供定時服務,也是可選任務,由巨集OS_CFG_TMR_EN控制是否使用此任務;
- 中斷服務管理任務:可選任務,由巨集OS_CFG_ISR_POST_DEFERRED_EN控制是否使用此任務。
UCOSIII空閒任務
我們首先來看一下空閒任務:OS_IdleTask(),在os_core.c檔案中定義。任務OS_IdleTask()是必須建立的,不過不需要手動建立,在呼叫OS_Init()初始化UCOS的時候就會被建立。
void OS_IdleTaskInit (OS_ERR *p_err) { #ifdef OS_SAFETY_CRITICAL if (p_err == (OS_ERR *)0) { OS_SAFETY_CRITICAL_EXCEPTION(); return; } #endif OSIdleTaskCtr = (OS_IDLE_CTR)0; /* ---------------- CREATE THE IDLE TASK ---------------- */ OSTaskCreate((OS_TCB *)&OSIdleTaskTCB, (CPU_CHAR *)((void *)"uC/OS-III Idle Task"), (OS_TASK_PTR)OS_IdleTask, (void *)0, (OS_PRIO )(OS_CFG_PRIO_MAX - 1u), (CPU_STK *)OSCfg_IdleTaskStkBasePtr, (CPU_STK_SIZE)OSCfg_IdleTaskStkLimit, (CPU_STK_SIZE)OSCfg_IdleTaskStkSize, (OS_MSG_QTY )0u, (OS_TICK )0u, (void *)0, (OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR | OS_OPT_TASK_NO_TLS), (OS_ERR *)p_err); }
從上面的程式碼可以看出,函式OS_IdleTaskInit()很簡單,只是呼叫了OSTaskCreate()來建立一 個 任 務 , 這 個 任 務 就 是 空 閒 任 務 。 任 務 優 先 級 為OS_CFG_PRIO_MAX –1,OS_CFG_PRIO_MAX是一個巨集,在檔案os_cfg.h中定義,OS_CFG_PRIO_MAX定義了UCOSIII可用的任務數。
空閒任務堆疊大小為OSCfg_IdleTaskStkSize,OSCfg_IdleTaskStkSize也是一個巨集,在os_cfg_app.c檔案中定義,預設為128,則空閒任務堆疊預設為128*4=512位元組。
空閒任務的任務函式為任務函式為OS_IdleTask(),OS_IdleTask()函式程式碼如下:
void OS_IdleTask (void *p_arg)
{
CPU_SR_ALLOC();
p_arg = p_arg; /* 消除警告 */
while (DEF_ON) {
CPU_CRITICAL_ENTER(); //進入臨界程式碼保護
OSIdleTaskCtr++;
#if OS_CFG_STAT_TASK_EN > 0u //開啟了統計任務
OSStatTaskCtr++;
#endif
CPU_CRITICAL_EXIT();
OSIdleTaskHook(); /* 鉤子函式 */
}
}
可以看到:每進入一次空閒任務,OSIdleTaskCtr就加一。我們可以通過檢視OSIdleTaskCtr變數的遞增速度來判斷CPU執行應用任務的繁忙程度,如果遞增的快的話說明應用任務花費時間少,很快就執行完了。
空閒任務特點:
- 空閒任務是UCOSIII建立的第一個任務;
- 空閒任務是UCOSIII必須建立的;
- 空閒任務優先順序總是為OS_CFG_PRIO_MAK-1;
- 空閒任務中不能呼叫任何可使空閒任務進入等待態的函式!
UCOSIII時鐘節拍任務
另一個必須建立的任務時鐘節拍任務:OS_Ticktask(),在OS_Init()中呼叫了一個函式OS_TickTaskInit(),函式程式碼如下:
void OS_TickTaskInit (OS_ERR *p_err)
{
#ifdef OS_SAFETY_CRITICAL
if (p_err == (OS_ERR *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
return;
}
#endif
OSTickCtr = (OS_TICK)0u; /* Clear the tick counter */
OSTickTaskTimeMax = (CPU_TS)0u;
OS_TickListInit(); /* Initialize the tick list data structures */
/* ---- CREATE THE TICK TASK ---- */
if (OSCfg_TickTaskStkBasePtr == (CPU_STK *)0) {
*p_err = OS_ERR_TICK_STK_INVALID;
return;
}
if (OSCfg_TickTaskStkSize < OSCfg_StkSizeMin) {
*p_err = OS_ERR_TICK_STK_SIZE_INVALID;
return;
}
if (OSCfg_TickTaskPrio >= (OS_CFG_PRIO_MAX - 1u)) { /* Only one task at the 'Idle Task' priority */
*p_err = OS_ERR_TICK_PRIO_INVALID;
return;
}
OSTaskCreate((OS_TCB *)&OSTickTaskTCB,
(CPU_CHAR *)((void *)"uC/OS-III Tick Task"),
(OS_TASK_PTR )OS_TickTask,
(void *)0,
(OS_PRIO )OSCfg_TickTaskPrio,
(CPU_STK *)OSCfg_TickTaskStkBasePtr,
(CPU_STK_SIZE)OSCfg_TickTaskStkLimit,
(CPU_STK_SIZE)OSCfg_TickTaskStkSize,
(OS_MSG_QTY )0u,
(OS_TICK )0u,
(void *)0,
(OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR | OS_OPT_TASK_NO_TLS),
(OS_ERR *)p_err);
}
可以看到在函式OS_TickTaskInit()的最後呼叫OSTaskCreate()來建立了一個任務,任務函式為OS_TickTask(),所以說時鐘節拍任務是UCOSIII必須建立的,同樣,不需要我們手工建立。時鐘節拍任務的任務優先順序為OSCfg_TickTaskPrio,時鐘節拍任務的優先順序儘可能的高一點,一般設定時鐘節拍任務的任務優先順序為1。
時鐘節拍任務的作用是跟蹤正在延時的任務,以及在指定時間內等待某個核心物件的任務,OS_TickTask()任務函式程式碼如下:
void OS_TickTask (void *p_arg)
{
OS_ERR err;
CPU_TS ts;
p_arg = p_arg; /* Prevent compiler warning */
while (DEF_ON) {
(void)OSTaskSemPend((OS_TICK )0, //請求訊號量
(OS_OPT )OS_OPT_PEND_BLOCKING,
(CPU_TS *)&ts,
(OS_ERR *)&err); /* Wait for signal from tick interrupt */
if (err == OS_ERR_NONE) {
if (OSRunning == OS_STATE_OS_RUNNING) {
OS_TickListUpdate(); /* 訊號量請求成功的話就呼叫 */
}
}
}
}
我們先來了解時鐘節拍任務中一個重要的概念:時鐘節拍列表。
時鐘節拍列表是由一個數據表OSCfg_TickWheel(在os_cfg_app.c中定義)和一個計數器OSTickCtr組 成 , 表OSCfg_TickWheel是 一 個 數 組 , 數 組 元 素 個 數 由 巨集OS_CFG_TICK_WHEEL_SIZE定義,巨集OS_CFG_TICK_WHEEL_SIZE在os_cfg_app.h中定義了。表OSCfg_TickWheel中的元素為os_tick_spoke型別的,os_tick_spoke是一個結構體,結構體定義如下:
struct os_tick_spoke {
OS_TCB *FirstPtr; /* 指標變數,在表頭上並屬於該表 */
OS_OBJ_QTY NbrEntries; /* 表示在該表項上等待的任務的數目 */
OS_OBJ_QTY NbrEntriesMax; /* 表示在該表項上等待的任務的最大數目 */
};
在使用時鐘節拍列表時需要先初始化時鐘節拍列表,在OS_TickTaskInit()函式中會呼叫OS_TickListInit()來初始化時鐘節拍列表,函式OS_TickListInit()程式碼如下:
void OS_TickListInit (void)
{
OS_TICK_SPOKE_IX i;
OS_TICK_SPOKE *p_spoke;
for (i = 0u; i < OSCfg_TickWheelSize; i++) {
p_spoke = (OS_TICK_SPOKE *)&OSCfg_TickWheel[i];
p_spoke->FirstPtr = (OS_TCB *)0;
p_spoke->NbrEntries = (OS_OBJ_QTY )0u;
p_spoke->NbrEntriesMax = (OS_OBJ_QTY )0u;
}
}
初始化時鐘節拍列表十分簡單,就是將OSCfg_TickWheel[]中的每個變數都清零。
UCOSIII統計任務
在UCOSIII中統計任務可用來統計CPU的使用率、各個任務的CPU使用率和各任務的堆疊使用情況,預設情況下統計任務是不會建立的,如果要使能統計任務的話需要將巨集OS_CFG_STAT_TASK_EN置1,巨集OS_CFG_STAT_TASK_EN在os_cfg.h檔案中有定義。
當我們將巨集OS_CFG_STAT_TASK_EN置1以後,OSinit()函式中有關統計任務的程式碼就可以編譯了。OS_StatTaskInit()函式用來建立統計任務,統計任務的優先順序通過巨集OS_CFG_STAT_TASK_PRIO設 置 ,一般將統計任務的優先順序設定為OS_CFG_PRIO_MAX-2,也就是倒數第二。
如果要使用統計任務的話就需要在main()函式建立的第一個也是唯一一個應用任務中呼叫OSStatTaskCPUUsageInit()函式。也就是在start_task()任務中呼叫函式:
void start_task(void *p_arg)
{
OS_ERR err;
CPU_SR_ALLOC();
p_arg = p_arg;
CPU_Init();
#if OS_CFG_STAT_TASK_EN > 0u
OSStatTaskCPUUsageInit(&err); //統計任務
#endif
#ifdef CPU_CFG_INT_DIS_MEAS_EN //如果使能了測量中斷關閉時間
CPU_IntDisMeasMaxCurReset();
#endif
#if OS_CFG_SCHED_ROUND_ROBIN_EN //當使用時間片輪轉的時候
//使能時間片輪轉排程功能,時間片長度為1個系統時鐘節拍,既1*5=5ms
OSSchedRoundRobinCfg(DEF_ENABLED,1,&err);
#endif
OS_CRITICAL_ENTER(); //進入臨界區
... //函式省略
OS_TaskSuspend((OS_TCB*)&StartTaskTCB,&err); //掛起開始任務
OS_CRITICAL_EXIT(); //進入臨界區
}
CPU的總的使用率會儲存在變數OSStatTaskCPUUsage中,我們可以通過讀取這個值來獲取CPU的使用率。從V3.03.00版本起,CPU的使用率用一個0~10000之間的整數表示(對應0.00~100.00%),在這之前的版本,CPU使用率是0~100之間的整數表示。
如果將巨集OS_CFG_STAT_TASK_STK_CHK_EN置1 的話表示檢查任務堆疊使用情況,那麼統計任務就會呼叫OSTaskStkChk()函式來計算所有已建立任務的堆疊使用量,並將檢測結果寫入到每個任務的OS_TCB中的StkFree和StkUsed中。
UCOSIII定時任務
UCOSIII提供軟體定時器功能,定時任務是可選的,將巨集OS_CFG_TMR_EN設定為1就會使能定時任務,在OSInit()中將會呼叫函式OS_TmrInit()來建立定時任務。定時任務的優先順序通過巨集OS_CFG_TMR_TASK_PRIO定義,一般將定時器任務優先順序設定為2。
UCOSIII中斷服務管理任務
當把os_cfg.h檔案中的巨集OS_CFG_ISR_POST_DEFERRED_EN置1就會使能中斷服務管理任務,UCOSIII會建立一個名為OS_IntQTask()的任務,該任務負責“延遲”在ISR中呼叫的系統post服務函式的行為。中斷服務管理任務的任務優先順序永遠是最高的,為0!
在UCOS中可以通過關閉中斷和任務排程器上鎖兩種方式來管理臨界段程式碼,如果採用後一種,即排程器上鎖的方式來管理臨界段程式碼的話,那麼在中斷服務函式中呼叫的“post”類函式就不允許操作諸如任務就緒表、等待表等系統內部資料結構。
當ISR(中斷服務函式)呼叫UCOSIII提供的“post”函式時,要傳送的資料和傳送的目的地都會存入一個特別的緩衝佇列中,當所有巢狀的ISR都執行完成以後UCOSIII會做任務切換,執行中斷服務管理任務,該任務會把快取佇列中存放的資訊重發給相應的任務。這樣做的好處就是可以減少中斷關閉的時間,否則,在ISR中還需要把任務從等待列表中刪除,並把任務放入就緒表,以及做一些其他的耗時操作。
UCOSIII鉤子函式
鉤子函式一般主要是用來擴充套件其他函式(任務)功能的,鉤子函式有如下幾個:
- OSIdleTaskHook(),空閒任務呼叫這個函式,可以用來讓CPU進入低功耗模式;
- OSInitHook(),系統初始化函式OSInit()呼叫此函式;
- OSStatTaskHook(),統計任務每秒中都會呼叫這個函式,此函式允許你向統計任務中新增自己的應用函式;
- OSTaskCreateHook(),任務建立的鉤子函式;
- OSTaskDelHook(),任務刪除的鉤子函式;
- OSTaskReturnHook(),任務意外返回時呼叫的鉤子函式,比如刪除某個任務;
- OSTaskSwHook(),任務切換時候呼叫的鉤子函式;
- OSTimeTickHook(),滴答定時器呼叫的鉤子函式。
本質上講:鉤子函式實際就是事件,就是你可以不改動UCOSIII原始碼結構,在需要的地方加入自己程式碼!
下面以OSIdleTaskHook()為例介紹一下:
void OS_IdleTask (void *p_arg)
{
CPU_SR_ALLOC();
p_arg = p_arg; /* 消除警告 */
while (DEF_ON) {
CPU_CRITICAL_ENTER(); //進入臨界程式碼保護
OSIdleTaskCtr++;
#if OS_CFG_STAT_TASK_EN > 0u //開啟了統計任務
OSStatTaskCtr++;
#endif
CPU_CRITICAL_EXIT();
OSIdleTaskHook(); /* 鉤子函式 */
}
}
函式OSIdleTaskHook(),程式碼如下:
void OSIdleTaskHook (void)
{
#if OS_CFG_APP_HOOKS_EN > 0u
if (OS_AppIdleTaskHookPtr != (OS_APP_HOOK_VOID)0) {
(*OS_AppIdleTaskHookPtr)();
}
#endif
}
從 上 面 的 函 數 代 碼 中 可 以 看 出 要 使 用 空 閒 任 務 鉤 子 函 數 的 話 需 要 將 巨集OS_CFG_APP_HOOKS_EN置1,即允許使用空閒任務的鉤子函式。當時使能空閒任務的鉤子函式以後每次進入空閒任務就會呼叫指標OS_AppIdleTaskHookPtr,而這個指標是一個指向函式的指標。OS_AppIdleTaskHookPtr是何方神聖?
開啟os_app_hooks.c文 件 , 在 文 件 中 有 個 函 數App_OS_SetAllHooks(),函式程式碼如下:
void App_OS_SetAllHooks (void)
{
#if OS_CFG_APP_HOOKS_EN > 0u
CPU_SR_ALLOC();
CPU_CRITICAL_ENTER();
OS_AppTaskCreateHookPtr = App_OS_TaskCreateHook;
OS_AppTaskDelHookPtr = App_OS_TaskDelHook;
OS_AppTaskReturnHookPtr = App_OS_TaskReturnHook;
OS_AppIdleTaskHookPtr = App_OS_IdleTaskHook;
OS_AppStatTaskHookPtr = App_OS_StatTaskHook;
OS_AppTaskSwHookPtr = App_OS_TaskSwHook;
OS_AppTimeTickHookPtr = App_OS_TimeTickHook;
CPU_CRITICAL_EXIT();
#endif
}
顯示將App_OS_IdleTaskHook賦值給OS_AppIdleTaskHookPtr。那麼問題來了,OS_AppIdleTaskHookPtr又是何方神聖?我們仔細檢視os_app_hooks.c檔案,會發現App_OS_IdleTaskHook是一個函式,程式碼如下:
void App_OS_IdleTaskHook (void)
{
}
到這裡我們基本就懂了空閒任務的鉤子函式OSIdleTaskHook()是怎麼工作了,在OSIdleTaskHook中最終呼叫的是函式App_OS_IdleTaskHook(),也就是說如果我們要想在空閒任務的鉤子函式中做一些其他處理的話就需要將程式程式碼寫在App_OS_IdleTaskHook()函式中。
注意:在空閒任務的鉤子函式中不能呼叫任何可以使空閒進入等待態的程式碼!
原因很簡單,CPU總是在不停的執行,需要一直工作,不能讓CPU停下來,哪怕是執行一些對應用沒有任何用的程式碼,比如簡單的將一個變數加一。在UCOS中為了讓CPU一直工作,在所有應用任務都進入等待態的時候CPU會執行空閒任務,我們可以從空閒任務的任務函式OS_IdleTask()看出,在OS_IdleTask()中沒有任何可以讓空閒任務進入等待態的程式碼。如果在OS_IdleTask()中有可以讓空閒任務進入等待態的程式碼的話有可能會在同一時刻所有任務(應用任務和空閒任務)同時進入等待態,此時CPU就會無所事事了,所以在空閒任務的鉤子函式OSIdleTaskHook()中不能出現可以讓空閒任務進入等待態的程式碼!這點很重要,一定要謹記!