uC/OS-II 内核源码分析(三)---时间管理机制详解
uC/OS-II时间管理相对比较简单,大体包含定时器创建、启动、删除,systick中断isr,timer线程处理,可能会涉及到任务调度,优先级反转,线程同步相关知识,本部分内容重点讲解定时器机制,其他内容,可在本人博客中的其他文章查询,欢迎提出宝贵意见,共同学习uC/OS-II,本书遵循先总后分的一贯做法,希望大家提出宝贵建议和意见。
一.废话不多讲,直接上图
上图基本涵盖了定时器的全部内容,首先大概解释下上图的内容,uC/OS中的定时器机制,是靠芯片内部的systick驱动的,当然也可以用其他的timer(已超出本文范围),uC/OS内核的定时机制是一种软定时机制,这样就具有很好的跨平台性,系统在每个systick中断到来时,在中断处理函数中释放定时器线程信号量,同时将各线程的tcb结构体的OSTCBDly成员减1,当减为0时,将对应的线程tcb移动到就绪队列表中(关于任务调度内容会在后续的博客中持续更新),在退出中断处理函数时,触发一次pendsv异常,进行任务上下文切换,此时定时器处理线程从阻塞状态恢复到运行状态(当有更高优先级的任务时例外),开始递减每个定时器的计数值,当发现超时时,执行对应的处理函数。
下面逐条逐个函数进行分析
二. 创建定时器
/*
*********************************************************************************************************
* CREATE A TIMER
*
* Description: This function is called by your application code to create a timer.
*
* Arguments : dly Initial delay.
* If the timer is configured for ONE-SHOT mode, this is the timeout used.
* If the timer is configured for PERIODIC mode, this is the first timeout to
* wait for before the timer starts entering periodic mode.
*
* period The 'period' being repeated for the timer.
* If you specified 'OS_TMR_OPT_PERIODIC' as an option, when the timer
* expires, it will automatically restart with the same period.
*
* opt Specifies either:
* OS_TMR_OPT_ONE_SHOT The timer counts down only once
* OS_TMR_OPT_PERIODIC The timer counts down and then reloads itself
*
* callback Is a pointer to a callback function that will be called when the timer expires.
* The callback function must be declared as follows:
*
* void MyCallback (OS_TMR *ptmr, void *p_arg);
*
* callback_arg Is an argument (a pointer) that is passed to the callback function when it is called.
*
* pname Is a pointer to an ASCII string that is used to name the timer. Names are
* useful for debugging.
*
* perr Is a pointer to an error code. '*perr' will contain one of the following:
* OS_ERR_NONE
* OS_ERR_TMR_INVALID_DLY you specified an invalid delay
* OS_ERR_TMR_INVALID_PERIOD you specified an invalid period
* OS_ERR_TMR_INVALID_OPT you specified an invalid option
* OS_ERR_TMR_ISR if the call was made from an ISR
* OS_ERR_TMR_NON_AVAIL if there are no free timers from the timer pool
*
* Returns : A pointer to an OS_TMR data structure.
* This is the 'handle' that your application will use to reference the timer created.
*********************************************************************************************************
*/
OS_TMR *OSTmrCreate (INT32U dly,
INT32U period,
INT8U opt,
OS_TMR_CALLBACK callback,
void *callback_arg,
INT8U *pname,
INT8U *perr)
{
OS_TMR *ptmr;
#ifdef OS_SAFETY_CRITICAL
if (perr == (INT8U *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
return ((OS_TMR *)0);
}
#endif
#ifdef OS_SAFETY_CRITICAL_IEC61508
if (OSSafetyCriticalStartFlag == OS_TRUE) {
OS_SAFETY_CRITICAL_EXCEPTION();
return ((OS_TMR *)0);
}
#endif
#if OS_ARG_CHK_EN > 0u
switch (opt) { /* Validate arguments */
case OS_TMR_OPT_PERIODIC:
if (period == 0u) {
*perr = OS_ERR_TMR_INVALID_PERIOD;
return ((OS_TMR *)0);
}
break;
case OS_TMR_OPT_ONE_SHOT:
if (dly == 0u) {
*perr = OS_ERR_TMR_INVALID_DLY;
return ((OS_TMR *)0);
}
break;
default:
*perr = OS_ERR_TMR_INVALID_OPT;
return ((OS_TMR *)0);
}
#endif
if (OSIntNesting > 0u) { /* See if trying to call from an ISR */
*perr = OS_ERR_TMR_ISR;
return ((OS_TMR *)0);
}
OSSchedLock();
ptmr = OSTmr_Alloc(); /* Obtain a timer from the free pool */
if (ptmr == (OS_TMR *)0) {
OSSchedUnlock();
*perr = OS_ERR_TMR_NON_AVAIL;
return ((OS_TMR *)0);
}
ptmr->OSTmrState = OS_TMR_STATE_STOPPED; /* Indicate that timer is not running yet */
ptmr->OSTmrDly = dly;
ptmr->OSTmrPeriod = period;
ptmr->OSTmrOpt = opt;
ptmr->OSTmrCallback = callback;
ptmr->OSTmrCallbackArg = callback_arg;
#if OS_TMR_CFG_NAME_EN > 0u
if (pname == (INT8U *)0) { /* Is 'pname' a NULL pointer? */
ptmr->OSTmrName = (INT8U *)(void *)"?";
} else {
ptmr->OSTmrName = pname;
}
#endif
OSSchedUnlock();
*perr = OS_ERR_NONE;
return (ptmr);
}
定时器创建函数注释非常详细,大概浏览下代码,可以看出只是从OSTmrFreeList中拿出空闲的一个OS_TMR类型的指针,用于创建新的定时器,同时对callback,定时时间,定时周期进行一些设置,没啥好讲的,自己看代码吧
二. 启动定时器
/*
*********************************************************************************************************
* START A TIMER
*
* Description: This function is called by your application code to start a timer.
*
* Arguments : ptmr Is a pointer to an OS_TMR
*
* perr Is a pointer to an error code. '*perr' will contain one of the following:
* OS_ERR_NONE
* OS_ERR_TMR_INVALID
* OS_ERR_TMR_INVALID_TYPE 'ptmr' is not pointing to an OS_TMR
* OS_ERR_TMR_ISR if the call was made from an ISR
* OS_ERR_TMR_INACTIVE if the timer was not created
* OS_ERR_TMR_INVALID_STATE the timer is in an invalid state
*
* Returns : OS_TRUE if the timer was started
* OS_FALSE if an error was detected
*********************************************************************************************************
*/
#if OS_TMR_EN > 0u
BOOLEAN OSTmrStart (OS_TMR *ptmr,
INT8U *perr)
{
#ifdef OS_SAFETY_CRITICAL
if (perr == (INT8U *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
return (OS_FALSE);
}
#endif
#if OS_ARG_CHK_EN > 0u
if (ptmr == (OS_TMR *)0) {
*perr = OS_ERR_TMR_INVALID;
return (OS_FALSE);
}
#endif
if (ptmr->OSTmrType != OS_TMR_TYPE) { /* Validate timer structure */
*perr = OS_ERR_TMR_INVALID_TYPE;
return (OS_FALSE);
}
if (OSIntNesting > 0u) { /* See if trying to call from an ISR */
*perr = OS_ERR_TMR_ISR;
return (OS_FALSE);
}
OSSchedLock();
switch (ptmr->OSTmrState) {
case OS_TMR_STATE_RUNNING: /* Restart the timer */
OSTmr_Unlink(ptmr); /* ... Stop the timer */
OSTmr_Link(ptmr, OS_TMR_LINK_DLY); /* ... Link timer to timer wheel */
OSSchedUnlock();
*perr = OS_ERR_NONE;
return (OS_TRUE);
case OS_TMR_STATE_STOPPED: /* Start the timer */
case OS_TMR_STATE_COMPLETED:
OSTmr_Link(ptmr, OS_TMR_LINK_DLY); /* ... Link timer to timer wheel */
OSSchedUnlock();
*perr = OS_ERR_NONE;
return (OS_TRUE);
case OS_TMR_STATE_UNUSED: /* Timer not created */
OSSchedUnlock();
*perr = OS_ERR_TMR_INACTIVE;
return (OS_FALSE);
default:
OSSchedUnlock();
*perr = OS_ERR_TMR_INVALID_STATE;
return (OS_FALSE);
}
}
#endif
通过OSTmr_Unlink和OSTmr_Link,将定时器结构体删除或加入OSTmrWheelTbl数组中,注意:
#define OS_TMR_CFG_WHEEL_SIZE 8 /* Size of timer wheel (#Spokes)
OS_EXT OS_TMR_WHEEL OSTmrWheelTbl[OS_TMR_CFG_WHEEL_SIZE];
该数组有八个元素,分别根据定时的时间对8取余,放入对应的下标的数组中,如下图所示:
三. 删除定时器OSTmrDel和停止定时器OSTmrStop自己看,比较简单
四. Systick中断处理函数
/*
*********************************************************************************************************
* SYS TICK HANDLER
*
* Description: Handle the system tick (SysTick) interrupt, which is used to generate the uC/OS-II tick
* interrupt.
*
* Arguments : none.
*
* Note(s) : 1) This function MUST be placed on entry 15 of the Cortex-M3 vector table.
*********************************************************************************************************
*/
void OS_CPU_SysTickHandler (void)
{
OS_CPU_SR cpu_sr;
OS_ENTER_CRITICAL(); /* Tell uC/OS-II that we are starting an ISR */
OSIntNesting++;
OS_EXIT_CRITICAL();
OSTimeTick(); /* Call uC/OS-II's OSTimeTick() */
OSIntExit(); /* Tell uC/OS-II that we are leaving the ISR */
}
该函数主要是递增中断嵌套引用计数OSIntNesting,执行任务延迟,释放信号量操作OSTimeTick,以及任务上下午切换
OSIntExit,
具体讲下OSTimeTick()函数,
/*
*********************************************************************************************************
* PROCESS SYSTEM TICK
*
* Description: This function is used to signal to uC/OS-II the occurrence of a 'system tick' (also known
* as a 'clock tick'). This function should be called by the ticker ISR but, can also be
* called by a high priority task.
*
* Arguments : none
*
* Returns : none
*********************************************************************************************************
*/
void OSTimeTick (void)
{
OS_TCB *ptcb;
#if OS_TICK_STEP_EN > 0u
BOOLEAN step;
#endif
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
#if OS_TIME_TICK_HOOK_EN > 0u
OSTimeTickHook(); /* Call user definable hook */ ---------1
#endif
#if OS_TIME_GET_SET_EN > 0u
OS_ENTER_CRITICAL(); /* Update the 32-bit tick counter */
OSTime++;
OS_EXIT_CRITICAL();
#endif
if (OSRunning == OS_TRUE) {
#if OS_TICK_STEP_EN > 0u
switch (OSTickStepState) { /* Determine whether we need to process a tick */
case OS_TICK_STEP_DIS: /* Yes, stepping is disabled */
step = OS_TRUE;
break;
case OS_TICK_STEP_WAIT: /* No, waiting for uC/OS-View to set ... */
step = OS_FALSE; /* .. OSTickStepState to OS_TICK_STEP_ONCE */
break;
case OS_TICK_STEP_ONCE: /* Yes, process tick once and wait for next ... */
step = OS_TRUE; /* ... step command from uC/OS-View */
OSTickStepState = OS_TICK_STEP_WAIT;
break;
default: /* Invalid case, correct situation */
step = OS_TRUE;
OSTickStepState = OS_TICK_STEP_DIS;
break;
}
if (step == OS_FALSE) { /* Return if waiting for step command */
return;
}
#endif
ptcb = OSTCBList; /* Point at first TCB in TCB list */
while (ptcb->OSTCBPrio != OS_TASK_IDLE_PRIO) { /* Go through all TCBs in TCB list */
OS_ENTER_CRITICAL();
if (ptcb->OSTCBDly != 0u) { /* No, Delayed or waiting for event with TO */
ptcb->OSTCBDly--; /* Decrement nbr of ticks to end of delay */
if (ptcb->OSTCBDly == 0u) { /* Check for timeout */
if ((ptcb->OSTCBStat & OS_STAT_PEND_ANY) != OS_STAT_RDY) {
ptcb->OSTCBStat &= (INT8U)~(INT8U)OS_STAT_PEND_ANY; /* Yes, Clear status flag */
ptcb->OSTCBStatPend = OS_STAT_PEND_TO; /* Indicate PEND timeout */
} else {
ptcb->OSTCBStatPend = OS_STAT_PEND_OK;
}
if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY) { /* Is task suspended? */
OSRdyGrp |= ptcb->OSTCBBitY; /* No, Make ready */
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
}
}
}
ptcb = ptcb->OSTCBNext; /* Point at next TCB in TCB list */
OS_EXIT_CRITICAL();
}
}
}
#if (OS_CPU_HOOKS_EN > 0u) && (OS_TIME_TICK_HOOK_EN > 0u)
void OSTimeTickHook (void)
{
#if OS_APP_HOOKS_EN > 0u
App_TimeTickHook();
#endif
#if OS_TMR_EN > 0u
OSTmrCtr++;
if (OSTmrCtr >= (OS_TICKS_PER_SEC / OS_TMR_CFG_TICKS_PER_SEC)) {
OSTmrCtr = 0;
OSTmrSignal();
}
#endif
}
#endif
上面函数
#if OS_TMR_EN > 0u
INT8U OSTmrSignal (void)
{
INT8U err;
err = OSSemPost(OSTmrSemSignal);
return (err);
}
#endif
即为释放信号量触发时间管理线程执行,OSTimeTick函数的其他部分是用来递减任务tcb结构体中delay的时间,当发现超时时,将相应的任务pend标志置为OS_STAT_PEND_TO或OS_STAT_PEND_OK,这两个标志的意思在后续任务调度的博客中会详细介绍,并清除任务阻塞标志(队列,信号量,消息邮箱,锁,等待标志位),并根据任务是否具备就绪状态的资格将任务加入到任务就绪表中,等待任务调度。
OSIntExit函数用于退出中断处理函数,并触发pendsv异常,用于触发任务上下文切换,
五.OSTmr_Task 时间管理线程
/*
*********************************************************************************************************
* TIMER MANAGEMENT TASK
*
* Description: This task is created by OSTmrInit().
*
* Arguments : none
*
* Returns : none
*********************************************************************************************************
*/
#if OS_TMR_EN > 0u
static void OSTmr_Task (void *p_arg)
{
INT8U err;
OS_TMR *ptmr;
OS_TMR *ptmr_next;
OS_TMR_CALLBACK pfnct;
OS_TMR_WHEEL *pspoke;
INT16U spoke;
p_arg = p_arg; /* Prevent compiler warning for not using 'p_arg' */
for (;;) {
OSSemPend(OSTmrSemSignal, 0u, &err); /* Wait for signal indicating time to update timers */
OSSchedLock();
OSTmrTime++; /* Increment the current time */
spoke = (INT16U)(OSTmrTime % OS_TMR_CFG_WHEEL_SIZE); /* Position on current timer wheel entry */
pspoke = &OSTmrWheelTbl[spoke];
ptmr = pspoke->OSTmrFirst;
while (ptmr != (OS_TMR *)0) {
ptmr_next = (OS_TMR *)ptmr->OSTmrNext; /* Point to next timer to update because current ... */
/* ... timer could get unlinked from the wheel. */
if (OSTmrTime == ptmr->OSTmrMatch) { /* Process each timer that expires */
OSTmr_Unlink(ptmr); /* Remove from current wheel spoke */
if (ptmr->OSTmrOpt == OS_TMR_OPT_PERIODIC) {
OSTmr_Link(ptmr, OS_TMR_LINK_PERIODIC); /* Recalculate new position of timer in wheel */
} else {
ptmr->OSTmrState = OS_TMR_STATE_COMPLETED; /* Indicate that the timer has completed */
}
pfnct = ptmr->OSTmrCallback; /* Execute callback function if available */
if (pfnct != (OS_TMR_CALLBACK)0) {
(*pfnct)((void *)ptmr, ptmr->OSTmrCallbackArg);
}
}
ptmr = ptmr_next;
}
OSSchedUnlock();
}
}
#endif
在收到信号量后,时间管理线程会根据当前时间计数值OSTmrTime对OS_TMR_CFG_WHEEL_SIZE取余,处理时间轮转表中对应的定时器,根据定时器的类型是否是周期的执行OSTmr_Unlink和OSTmr_Link操作,当定时器到时后执行对应的处理函数
(*pfnct)((void *)ptmr, ptmr->OSTmrCallbackArg);
以上即是对uC/OS-II内核定时器相关知识的整理,如想了解更多关于uC/OS-II的内核机制,请持续关注,谢谢!