目录
- 如何产生时基信号
- 系统时钟中断管理
- 时基任务
- 时基列表更新
- 写在最后
我在初学uC/OS-III的时候,时基产生后到底是如何去驱动操作系统运转的,对于这个问题一直有很多疑问,最后读了手册并且仔细推敲源码后终于理解,这里记录下来。
关于时钟节拍的意义,这里就不再赘述。我们只要知道操作系统依赖于时钟节拍推动 CPU 去执行指令就行了。下文的概述主要是针对STM32系列单片机。
如何产生时基信号
时钟节拍需要依赖于硬件定时器,STM32 通常使用内核定时器 SystemTick时钟作为时钟节拍的产生。因此我们在初始化代码的时候就需要配置SystemTick定时器。贴出部分代码如下:
CPU_INT32U cpu_clk_freq;
CPU_INT32U cnts;
cpu_clk_freq = BSP_CPU_ClkFreq();
cnts = cpu_clk_freq / (CPU_INT32U)OSCfg_TickRate_Hz;
OS_CPU_SysTickInit(cnts);
其中OSCfg_TickRate_Hz的值决定了UCOS-III的时钟节拍频率,在文件os_cfg_app.c中被如下定义:
OS_RATE_HZ const OSCfg_TickRate_Hz = (OS_RATE_HZ )OS_CFG_TICK_RATE_HZ;
然后我们再看OS_CFG_TICK_RATE_HZ的值,最后定义到文件os_cfg_app.h中
#define OS_CFG_TICK_RATE_HZ 1000u
也就是说,uCOS-III默认时钟节拍为1000Hz。这样就完成了对SystemTick的初始化,初始化后我们关心的就是SystemTick的中断服务函数了。
系统时钟中断管理
在uCOS-III中,SystemTick的中断服务函数由SysTick_Handler改为了OS_CPU_SysTickHandler,然后我们找到OS_CPU_SysTickHandler函数后可以看到如下代码:
void OS_CPU_SysTickHandler (void)
{
CPU_SR_ALLOC();
CPU_CRITICAL_ENTER();
OSIntNestingCtr++;
CPU_CRITICAL_EXIT();
OSTimeTick();
OSIntExit();
}
这里我们最关心的也就是OSTimeTick函数了,我们转到定义查看一下代码,如下:
void OSTimeTick (void)
{
OS_ERR err;
#if OS_CFG_ISR_POST_DEFERRED_EN > 0u
CPU_TS ts;
#endif
OSTimeTickHook();
#if OS_CFG_ISR_POST_DEFERRED_EN > 0u
ts = OS_TS_GET();
OS_IntQPost((OS_OBJ_TYPE) OS_OBJ_TYPE_TICK,
(void *)&OSRdyList[OSPrioCur],
(void *) 0,
(OS_MSG_SIZE) 0u,
(OS_FLAGS ) 0u,
(OS_OPT ) 0u,
(CPU_TS ) ts,
(OS_ERR *)&err);
#else
(void)OSTaskSemPost((OS_TCB *)&OSTickTaskTCB,
(OS_OPT ) OS_OPT_POST_NONE,
(OS_ERR *)&err);
#if OS_CFG_SCHED_ROUND_ROBIN_EN > 0u
OS_SchedRoundRobin(&OSRdyList[OSPrioCur]);
#endif
#if OS_CFG_TMR_EN > 0u
OSTmrUpdateCtr--;
if (OSTmrUpdateCtr == (OS_CTR)0u) {
OSTmrUpdateCtr = OSTmrUpdateCnt;
OSTaskSemPost((OS_TCB *)&OSTmrTaskTCB,
(OS_OPT ) OS_OPT_POST_NONE,
(OS_ERR *)&err);
}
#endif
#endif
}
此函数看起来很复杂,但是仔细分析后会发现,OS_CFG_ISR_POST_DEFERRED_EN这个宏是被默认设置为1的,所以整个代码条件编译完后关注的函数只有两个,第一个是OSTimeTickHook(); ,这是时基任务的钩子函数,里面打开后可以看见没有操作,第二个就只剩下这个函数了:
OS_IntQPost((OS_OBJ_TYPE) OS_OBJ_TYPE_TICK, /* Post to ISR queue */
(void *)&OSRdyList[OSPrioCur],
(void *) 0,
(OS_MSG_SIZE) 0u,
(OS_FLAGS ) 0u,
(OS_OPT ) 0u,
(CPU_TS ) ts,
(OS_ERR *)&err);
这个,然后我们会奇怪,这里面并没有进行任务调度的代码啊,这里也是初学者最容易发生疑惑的地方,就是并没有出现关于时基更新的函数啊,然后中断就结束了。
时基任务
其实这里用到了UCOS-III中消息传递方面的知识。我们知道,上面的函数都是在OS_CPU_SysTickHandler中执行了,而这又是一个中断服务函数,理论上中断服务函数要快进快出,于是UCOS-III为此,使用了一种叫做中断延迟发布的方法,让中断中需要发送的信息先保存在一个特殊的队列中,然后等到中断服务函数执行完后再去发布这个信号或者消息。
我们先转到下面这段程序,来看一下:
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;
OSTickTaskTimeMax = (CPU_TS)0u;
OS_TickListInit();
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)) {
*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);
}
这就是UCOS-III的时基任务的初始化,在我们对UCOS-III初始化的时候就已经被调用了,属于系统自己创建的任务,然后我们转到OS_TickTask的定义看一下:
void OS_TickTask (void *p_arg)
{
OS_ERR err;
CPU_TS ts;
p_arg = p_arg;
while (DEF_ON) {
(void)OSTaskSemPend((OS_TICK )0,
(OS_OPT )OS_OPT_PEND_BLOCKING,
(CPU_TS *)&ts,
(OS_ERR *)&err);
if (err == OS_ERR_NONE) {
if (OSRunning == OS_STATE_OS_RUNNING) {
OS_TickListUpdate();
}
}
}
}
这里其实我们就找到了真正完成UCOS-III系统时基更新的函数OS_TickListUpdate(),在查看这个函数之前,我们可以看到在这个while死循环中,一直在调用一个函数OSTaskSemPend,并且当返回的err是无错误,并且OS已经开始跑了,才会执行这个OS_TickListUpdate(),这里刚好就和上文一直留下的一个伏笔相呼应,它就是OS_IntQPost。
OSTaskSemPend会将OS_TickTask 挂起等待一个信号的到来才能继续执行,而OS_IntQPost就是提供这个信号的函数,这两者配合后,就完成了OS_CPU_SysTickHandler的任务,来引起时基列表的更新,也就是OS_TickListUpdate()。
时基列表更新
先列出代码:
void OS_TickListUpdate (void)
{
CPU_BOOLEAN done;
OS_TICK_SPOKE *p_spoke;
OS_TCB *p_tcb;
OS_TCB *p_tcb_next;
OS_TICK_SPOKE_IX spoke;
CPU_TS ts_start;
CPU_TS ts_end;
CPU_SR_ALLOC();
OS_CRITICAL_ENTER();
ts_start = OS_TS_GET();
OSTickCtr++;
spoke = (OS_TICK_SPOKE_IX)(OSTickCtr % OSCfg_TickWheelSize);
p_spoke = &OSCfg_TickWheel[spoke];
p_tcb = p_spoke->FirstPtr;
done = DEF_FALSE;
while (done == DEF_FALSE) {
if (p_tcb != (OS_TCB *)0) {
p_tcb_next = p_tcb->TickNextPtr;
switch (p_tcb->TaskState) {
case OS_TASK_STATE_RDY:
case OS_TASK_STATE_PEND:
case OS_TASK_STATE_SUSPENDED:
case OS_TASK_STATE_PEND_SUSPENDED:
break;
case OS_TASK_STATE_DLY:
p_tcb->TickRemain = p_tcb->TickCtrMatch
- OSTickCtr;
if (OSTickCtr == p_tcb->TickCtrMatch) {
p_tcb->TaskState = OS_TASK_STATE_RDY;
OS_TaskRdy(p_tcb);
} else {
done = DEF_TRUE;
}
break;
case OS_TASK_STATE_PEND_TIMEOUT:
p_tcb->TickRemain = p_tcb->TickCtrMatch
- OSTickCtr;
if (OSTickCtr == p_tcb->TickCtrMatch) {
#if (OS_MSG_EN > 0u)
p_tcb->MsgPtr = (void *)0;
p_tcb->MsgSize = (OS_MSG_SIZE)0u;
#endif
p_tcb->TS = OS_TS_GET();
OS_PendListRemove(p_tcb);
OS_TaskRdy(p_tcb);
p_tcb->TaskState = OS_TASK_STATE_RDY;
p_tcb->PendStatus = OS_STATUS_PEND_TIMEOUT;
p_tcb->PendOn = OS_TASK_PEND_ON_NOTHING;
} else {
done = DEF_TRUE;
}
break;
case OS_TASK_STATE_DLY_SUSPENDED:
p_tcb->TickRemain = p_tcb->TickCtrMatch
- OSTickCtr;
if (OSTickCtr == p_tcb->TickCtrMatch) {
p_tcb->TaskState = OS_TASK_STATE_SUSPENDED;
OS_TickListRemove(p_tcb);
} else {
done = DEF_TRUE;
}
break;
case OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED:
p_tcb->TickRemain = p_tcb->TickCtrMatch
- OSTickCtr;
if (OSTickCtr == p_tcb->TickCtrMatch) {
#if (OS_MSG_EN > 0u)
p_tcb->MsgPtr = (void *)0;
p_tcb->MsgSize = (OS_MSG_SIZE)0u;
#endif
p_tcb->TS = OS_TS_GET();
OS_PendListRemove(p_tcb);
OS_TickListRemove(p_tcb);
p_tcb->TaskState = OS_TASK_STATE_SUSPENDED;
p_tcb->PendStatus = OS_STATUS_PEND_TIMEOUT;
p_tcb->PendOn = OS_TASK_PEND_ON_NOTHING;
} else {
done = DEF_TRUE;
}
break;
default:
break;
}
p_tcb = p_tcb_next;
} else {
done = DEF_TRUE;
}
}
ts_end = OS_TS_GET() - ts_start;
if (OSTickTaskTimeMax < ts_end) {
OSTickTaskTimeMax = ts_end;
}
OS_CRITICAL_EXIT();
}
这里代码很长,可以 下去后慢慢分析,主要是关于时钟节拍列表的,这里不赘述只提一点,就是第31行的OSTickCtr++;,如下:
OSTickCtr++; /* Keep track of the number of ticks */
OSTickCtr变量是由系统创建的一个32bit无符号整型的全局变量,用来记录时钟节拍数,简单点理解就是,每当 OS_CPU_SysTickHandler 发生中断后,会给OS_TickTask 函数发送一个信号,收到信号后会调用 OS_TickListUpdate 来更新时钟节拍列表,并且使OSTickCtr加一,来驱动任务调度。
写在最后
uCOS-III作为一个能够上火星的操作系统,一定程度上反映了它的优越性,但是越是成熟的操作系统,就越会有很多难以理解或者不容易理解的地方,我作为一个初学者,会时常将遇到的一些问题或者难以理解的东西,这里解决后记录下来,希望可以给其他也遇到这个问题的同学提供一点帮助。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)