Understanding uC/OS-II (4)



    -- Time Management









在前面曾提到,uC/OS-II (其他內核也一樣) 要求使用者提供定時中斷來實現延時與超時控制等功能。這個週期性定時中斷叫做 clock tick,它應該每秒發生 10 至 100 次。 clock tick 的實際頻率是由使用者的應用程式決定的。 clock tick 的頻率越高,系統的負荷就越重。


在前面討論了 tick 的 ISR 和 OSTimeTick() 函式 -- 該函式用於通知 uC/OS-II 發生了 tick 中斷。本篇主要講述五個與 clock tick 有關的系統服務:




  • OSTimeDly()



  • OSTimeDlyHMSM()



  • OSTimeDlyResume()



  • OSTimeGet()



  • OSTimeSet()



本章所提到的函式可以在 OS_TIME.C 文件中找到。




Delaying a task, OSTimeDly()


uC/OS-II 提供了一個這樣系統服務:申請該服務的 task 可以延時一段時間,這段時間的長短是用 clock tick 的數目來決定的。實現這個系統服務的函式叫做 OSTimeDly()。呼叫該函式會使 uC/OS-II 進行一次 task 排程,並且執行下一個優先權最高且 READY 的 task。task 呼叫 OSTimeDly() 後,一旦規定的時間期滿或者有其他的 task 透過呼叫 OSTimeDlyResume() 取消了延時,它就會馬上進入 READY 狀態。注意,只有當該 task 在所有 READY 的 task 中具有最高的優先權時,它才會立即運行。


程式清單 L5.1 所示的是 task 延時函式 OSTimeDly() 的程式碼。使用者的應用程式是透過提供延時的 clock tick 數 -- 一個 1 到 65535 之間的數,來呼叫該函式的。如果使用者指定 0 值 [L5.1(1)],則表明使用者不想延時 task,函式會立即返回。非 0 值會使得 task 延時函式 OSTimeDly() 將目前 task 從 ready list 中移除 [L5.1(2)]。接著,這個延時 tick 數會被保存在目前 task 的 OS_TCB 中 [L5.1(3)],並且透過 OSTimeTick() 每隔一個 clock tick 就減少一個延時 tick 數。最後,既然 task 已經不再處於 READY 狀態,task 排程器會執行下一個優先權最高且 READY 的 task。








程式清單 L5.1 OSTimeDly()



void OSTimeDly (INT16U ticks)
{
if (ticks > 0) { (1)
OS_ENTER_CRITICAL();

if ((OSRdyTbl[OSTCBCur->OSTCBY] &= ~OSTCBCur->OSTCBBitX) == 0) (2)
OSRdyGrp &= ~OSTCBCur->OSTCBBitY;
}
OSTCBCur->OSTCBDly = ticks; (3)
OS_EXIT_CRITICAL();
OSSched(); (4)
}
}

清楚地認識 0 到一個 tick 之間的延時過程是非常重要的。換句話說,如果使用者只想延時一個 clock tick,而實際上是在 0 到一個 tick 之間結束延時。即使使用者的處理器的負荷不是很重,這種情況依然是存在的。圖 F5.1 詳細說明了整個過程。系統每隔 10ms 發生一次 clock tick 中斷 [F5.1(1)]。假如使用者沒有執行其他的中斷並且此時的中斷是開著的,clock tick 中斷服務就會發生 [F5.1(2)]。也許使用者有好幾個高優先權的 task (HPT) 在等待延時期滿,它們會接著執行 [F5.1(3)]。接下來,圖 F5.1 中所示的低優先權 task (LPT) 會得到執行的機會,該 task 在執行完後馬上呼叫圖 [F5.1(4)] 所示的 OSTimeDly(1)。uC/OS-II 會使該 task 處於休眠狀態直至下一個 tick 的到來。當下一個 tick 到來後,clock tick 的 ISR 會執行 [F5.1(5)],但是這一次由於沒有高優先權的 task 被執行,uC/OS-II 會立即執行申請延時一個 clock tick 的 task [F5.1(6)]。正如我們所看到的,該 task 實際的延時少於一個 tick!在負荷很重的系統中,task 甚至有可能會在 tick 中斷即將發生時呼叫 OSTimeDly(1),在這種情況下,task 幾乎就沒有得到任何延時,因為 task 馬上又被重新排程了。如果使用者的應用程式至少得延時一個 tick,必須要呼叫 OSTimeDly(2),指定延時兩個 tick!



Figure 5.1 Delay resolution.




Delaying a task, OSTimeDlyHMSM()


OSTimeDly() 雖然是一個非常有用的函式,但使用者的應用程式需如果需要知道延時時間對應的 clock tick 數目。使用者可以使用定義全局常數 OS_TICKS_PER_SEC (參看 OS_CFG.H) 的方法將時間轉換成時間片段值,但這種方法有時顯得比較愚笨。所以 uC/OS-II 增加了 OSTimeDlyHMSM() 函式後,使用者就可以按小時 (H)、分 (M)、秒 (S) 和毫秒 (m) 來定義時間了,這樣會顯得更自然些。與 OSTimeDly() 一樣,呼叫 OSTimeDlyHMSM() 函式也會使 uC/OS-II 進行一次 task 排程,並且執行下一個優先權最高且 READY 的 task。task 呼叫 OSTimeDlyHMSM() 後,一旦規定的時間期滿或者有其他的 task 透過呼叫 OSTimeDlyResume() 取消了延時,它就會馬上處於就緒態。同樣,只有當該 task 在所有就緒態 task 中具有最高的優先權時,它才會立即運行。


程式清單 L5.2 所示的是 OSTimeDlyHMSM() 的程式碼。從中可以看出,應用程式是透過用小時、分、秒和毫秒指定延時來呼叫該函式的。在實際應用中,使用者應避免使 task 延時過長的時間,因為從 task 中獲得一些反饋行為 (如減少計數器,清除 LED 等等)經常是很不錯的事。但是,如果使用者確實需要延時長時間的話,uC/OS-II 可以將 task 延時長達 256 個小時 (接近11天)。


OSTimeDlyHMSM() 一開始先要檢驗使用者是否為參數定義了有效的值 [L5.2(1)]。與 OSTimeDly() 一樣,即使使用者沒有定義延時, OSTimeDlyHMSM() 也是存在的 [L5.2(9)]。因為 uC/OS-II 只知道 tick,所以 tick 的總數是從指定的時間中計算出來的 [L5.2(3)]。很明顯,程式清單 L5.2 中的程式並不是十分有效的。uC/OS-II 只是用這種方法來告訴大家一個公式,這樣使用者就可以知道怎樣計算總 tick 數了。真正有意義的只是 OS_TICKS_PER_SEC。[L5.2(3)] 決定了最接近需要延遲的時間的 clock tick 總數。500 / OS_TICKS_PER_SECOND 的值基本上與 0.5 個 tick 對應的毫秒數相同。例如,若將 OS_TICKS_PER_SEC 設置成 100Hz (10ms),4ms 的延時不會產生任何延時!而 5ms 的延時就等於延時 10ms。


uC/OS-II 支援的延時最長為 65,535 個 tick。要想支援更長時間的延時,如 L5.2(2) 所示,OSTimeDlyHMSM() 確定了使用者想延時多少次超過 65,535 個 tick 的數目 [L5.2(4)] 和剩下的 tick 數 [L5.2(5)]。例如,若 OS_TICKS_PER_SEC 的值為 100,使用者想延時 15 分鐘,則 OSTimeDlyHMSM() 會延時 15x60x100=90,000 個 tick。這個延時會被分割成兩次 32,768 個 tick 的延時 (因為使用者只能延時 65,535 個 tick 而不是 65536 個 tick) 和一次 24,464 個 tick 的延時。在這種情況下,OSTimeDlyHMSM() 首先考慮剩下的 tick,然後是超過 65,535 的 tick 數 [L5.2(7) 和 (8)] (即兩個 32,768 個 tick 延時)。








程式清單 L5.2 OSTimeDlyHMSM()



INT8U OSTimeDlyHMSM (INT8U hours, INT8U minutes, INT8U seconds, INT16U milli)
{
INT32U ticks;
INT16U loops;


if (hours > 0 || minutes > 0 || seconds > 0 || milli > 0) { (1)
if (minutes > 59) {
return (OS_TIME_INVALID_MINUTES);
}
if (seconds > 59) {
return (OS_TIME_INVALID_SECONDS);
}

If (milli > 999) {
return (OS_TIME_INVALID_MILLI);
}

ticks = (INT32U) hours * 3600L * OS_TICKS_PER_SEC (2)
+ (INT32U) minutes * 60L * OS_TICKS_PER_SEC
+ (INT32U) seconds * OS_TICKS_PER_SEC
+ OS_TICKS_PER_SEC * ((INT32U) milli
+ 500L / OS_TICKS_PER_SEC) / 1000L; (3)

loops = ticks / 65536L; (4)
ticks = ticks % 65536L; (5)
OSTimeDly(ticks); (6)
while (loops > 0) { (7)
OSTimeDly(32768); (8)
OSTimeDly(32768);
loops--;
}
return (OS_NO_ERR);

} else {
return (OS_TIME_ZERO_DLY); (9)
}
}

由於 OSTimeDlyHMSM() 的實現方法,使用者不能結束延時呼叫 OSTimeDlyHMSM() 要求延時超過 65535 個 tick 的 task。換句話說,如果 clock tick 的頻率是 100Hz,使用者不能呼叫 OSTimeDlyHMSM(0, 10, 55, 350) 或更長延遲時間的 task。




Resuming a delayed task, OSTimeDlyResume()


uC/OS-II 允許使用者結束延時正處於延時中的 task。延時的 task 可以不等待延時期滿,而是透過其他 task 來取消延時讓使自己處於就緒態。這可以透過呼叫 OSTimeDlyResume() 和指定要恢復的 task 的優先權來完成。實際上,OSTimeDlyResume() 也可以喚醒正在等待事件 (參看 Intertask Communication & Synchronization) 的 task,雖然這一點並沒有提到過。在這種情況下,等待事件發生的 task 會考慮是否終止等待事件。


OSTimeDlyResume() 的程式碼如程式清單 L5.3 所示,它首先要確保指定的 task 優先權有效的 [L5.3(1)]。接著,OSTimeDlyResume() 要確認要結束延時的 task 是確實存在的 [L5.3(2)]。如果 task 存在,OSTimeDlyResume() 會檢驗 task 是否在等待延時期滿 [L5.3(3)]。只要 OS_TCB 資料結構中的 OSTCBDly 包含非 0 值就表明 task 正在等待延時期滿,因為 task 呼叫了 OSTimeDly(),OSTimeDlyHMSM() 或其他 PEND 函式。然後延時就可以透過強制命令 OSTCBDly 為 0 來取消 [L5.3(4)]。延時的 task 有可能已處於休眠的狀態了,這樣的話,task 只有在沒有處於休眠狀態的情況下才能轉為就緒的狀態 [L5.3(5)]。當上面的條件都滿足後,task 就會被放在 ready list 中 [L5.3(6)]。這時,OSTimeDlyResume() 會呼叫 task 排程器來看被恢復的 task 是否擁有比目前 task 更高的優先權 [L5.3(7)]。這將會導致 task switch。








程式清單 L5.3 Resuming a delayed task



INT8U OSTimeDlyResume (INT8U prio)
{
OS_TCB *ptcb;


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

OS_ENTER_CRITICAL();

ptcb = (OS_TCB *)OSTCBPrioTbl[prio];
if (ptcb != (OS_TCB *)0) { (2)
if (ptcb->OSTCBDly != 0) { (3)
ptcb->OSTCBDly = 0; (4)

if (!(ptcb->OSTCBStat & OS_STAT_SUSPEND)) { (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_TIME_NOT_DLY);
}
} else {
OS_EXIT_CRITICAL();
return (OS_TASK_NOT_EXIST);
}
}

注意,使用者的 task 有可能是透過 semaphore、mailbox 或 message queue 來延時自己的。所以也可以簡單地透過控制 semaphore、 mailbox 或 message queue 來恢復這樣的 task。這種情況存在的唯一問題是它會要求使用者分配 Event Control Block (ECB),因此使用者的應用程式會多佔用一些 RAM。




System time, OSTimeGet() and OSTimeSet()


無論 clock tick 何時發生,uC/OS-II 都會將一個 32 位元的計數器加 1。這個計數器為 0 時是在使用者呼叫 OSStart() 和 4,294,967,295 個 tick 執行完一遍的時候從 0 開始計數。在 clock tick 的頻率等於 100Hz 的時候,這個 32 位的計數器每隔 497 天就重新開始計數。使用者可以透過呼叫 OSTimeGet() 來獲得該計數器的目前值。也可以透過呼叫 OSTimeSet() 來改變該計數器的值。 OSTimeGet() 和 OSTimeSet() 兩個函式的程式碼如程式清單 L5.4 所示。注意,在訪問 OSTime 的時候中斷是關掉的。這是因為在大多數 8 位處理器上增加和拷貝一個 32 位元的數都需要數條指令,這些指令一般都需要一次執行完畢,而不能被中斷等因素打斷。








程式清單 L5.4 Obtaining and setting the system time



INT32U OSTimeGet (void)
{
INT32U ticks;


OS_ENTER_CRITICAL();

ticks = OSTime;

OS_EXIT_CRITICAL();
return (ticks);
}


void OSTimeSet (INT32U ticks)
{
OS_ENTER_CRITICAL();

OSTime = ticks;

OS_EXIT_CRITICAL();
}

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