快速回顾
本文进一步解析μC/OS-Ⅱ中,与时间相关的函数源码。
系统的心跳
在前面的源码解析中,更多是通过学习各种对象的函数源码来理解对象的运行逻辑,但函数只是一个接口(或者说是改变对象状态的入口),如果需要等待延时,系统是如何处理的呢?例如:我们在Pend信号量时,会传入一个等待超时值Delay,超时后谁来通知任务重新就绪?再比如,我们使用的软件定时器,到期后谁去通知执行回调函数?
在上一节的软件定时器里,已经通过案例引出了μC/OSⅡ的系统滴答机制,可以看作是系统的心跳 ,每一次跳动都相当于一次轮询,对μC/OSⅡ的各种延时等待进行管理。在μC/OSⅡ的内核里,提供了一个系统节拍服务函数**OSTimeTick()**用来处理等待过程中各种对象的状态变化,但使用它是有条件的,必须周期性地调用才有意义,就像心跳一样能够按固定频率跳动。
对此,μC/OSⅡ官方建议可以将该函数放在系统的滴答中断system tick中调用,或放在一个高优先级的任务中,目的就是为了周期且稳定地触发。
cpp
void SysTick_Handler(void)
{
OS_CPU_SR cpu_sr;
OS_ENTER_CRITICAL(); /* 进入临界区 */
OSIntNesting++;
OS_EXIT_CRITICAL();
OSTimeTick(); /* 系统滴答函数 */
OSIntExit();
}
具体看系统节拍服务函数源码OSTimeTick():
cpp
//os_core.c
void OSTimeTick (void)
{
OS_TCB *ptcb;
#if OS_TICK_STEP_EN > 0u
BOOLEAN step;
#endif
#if OS_CRITICAL_METHOD == 3u /* 初始化临界区变量 */
OS_CPU_SR cpu_sr = 0u;
#endif
#if OS_TIME_TICK_HOOK_EN > 0u
OSTimeTickHook(); /* 用户回调函数,如有设置定时器则可以在此函数释放定时器任务信号量 */
#endif
#if OS_TIME_GET_SET_EN > 0u
OS_ENTER_CRITICAL();
OSTime++; /* 系统绝对时间更新(系统节拍数) */
OS_EXIT_CRITICAL();
#endif
if (OSRunning == OS_TRUE) {
#if OS_TICK_STEP_EN > 0u
switch (OSTickStepState) { /* 查看系统节拍步进状态,来决定是否要处理当前节拍 */
case OS_TICK_STEP_DIS: /* 禁用步进状态,正常处理节拍 */
step = OS_TRUE;
break;
case OS_TICK_STEP_WAIT: /* 等待状态,等待被处理成OS_TICK_STEP_ONCE */
step = OS_FALSE; //不处理当次节拍
break;
case OS_TICK_STEP_ONCE: /* 单次处理 */
step = OS_TRUE;
OSTickStepState = OS_TICK_STEP_WAIT;
break;
default: /* 非法状态,修改为禁用步进状态 */
step = OS_TRUE;
OSTickStepState = OS_TICK_STEP_DIS;
break;
}
if (step == OS_FALSE) { /* 只有step为TRUE才能处理当次节拍 */
return;
}
#endif
ptcb = OSTCBList; /* 指向TCB链表头 */
while (ptcb->OSTCBPrio != OS_TASK_IDLE_PRIO) { /* 遍历所有任务的TCB */
OS_ENTER_CRITICAL();
if (ptcb->OSTCBDly != 0u) { /* 如果存在等待延时,则将延时-1 */
ptcb->OSTCBDly--;
if (ptcb->OSTCBDly == 0u) { /* 如果等待延时减为0,则设置为就绪 */
if ((ptcb->OSTCBStat & OS_STAT_PEND_ANY) != OS_STAT_RDY) {
ptcb->OSTCBStat &= (INT8U)~(INT8U)OS_STAT_PEND_ANY; /* 清除任务状态中的等待 */
ptcb->OSTCBStatPend = OS_STAT_PEND_TO; /* pend状态设置为超时 */
} else {
ptcb->OSTCBStatPend = OS_STAT_PEND_OK;
}
if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY) { /* 任务不能处于suspend状态 */
OSRdyGrp |= ptcb->OSTCBBitY; /* 在优先级就绪表中将相应位置1 */
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
}
}
}
ptcb = ptcb->OSTCBNext; /* 取链表中的下一个TCB */
OS_EXIT_CRITICAL();
}
}
}
主要做了三件事:
①如有设置定时器任务,则尝试释放定时器任务信号量;
②如有设置系统时间(系统节拍),则系统节拍数+1;
③如有设置节拍的步进状态,则根据步进状态判断是否要进入节拍服务。每次节拍服务的内容:遍历所有任务的TCB,将有等待延时的任务,其等待延时-1,如延时变为0,将其设置为就绪态并加入优先级就绪表。
延时服务
另一类和时间服务有关的函数就是系统时延了,比如OSTimeDly() 和OSTimeDlyHMSM() ,他们只有传入参数的区别,本质做的是一样的工作,即延时并尝试切换上下文(避免阻塞在当前任务)。
OSTimeDly(INT32U ticks)
传入参数ticks表示等待延时时间,也即系统的时钟节拍数。
cpp
//os_time.c
void OSTimeDly (INT32U ticks)
{
INT8U y;
#if OS_CRITICAL_METHOD == 3u /* 初始化临界区变量 */
OS_CPU_SR cpu_sr = 0u;
#endif
if (OSIntNesting > 0u) { /* 不能在中断中调用 */
return;
}
if (OSLockNesting > 0u) { /* 调度器不能上锁 */
return;
}
if (ticks > 0u) { /* 零延时没有意义 */
OS_ENTER_CRITICAL();
y = OSTCBCur->OSTCBY; /* 将当前任务从就绪表中清除 */
OSRdyTbl[y] &= (OS_PRIO)~OSTCBCur->OSTCBBitX;
if (OSRdyTbl[y] == 0u) {
OSRdyGrp &= (OS_PRIO)~OSTCBCur->OSTCBBitY;
}
OSTCBCur->OSTCBDly = ticks; /* 设置任务等待延时 */
OS_EXIT_CRITICAL();
OS_Sched(); /* 切换上下文 */
}
}
INT8U OSTimeDlyHMSM(INT8U hours, INT8U minutes, INT8U seconds, INT16U ms)
该函数和**OSTimeDly()**功能是一样的,只是传入的等待时间变成了日常理解的时分秒制。