前言
系统时钟节拍是多任务得以正常运行的基石,UCOS的系统时钟节拍一般依赖于MCU的硬件定时器.硬件定时器产生固定时间间隔的中断,中断中调用UCOS的系统函数,完成多任务操作系统的基本调度功能.
本文使用的UCOS版本:V2.91.
代码分析讲解
在本例中我们设定uCOS的时钟频率为100HZ,即10ms进入一次系统时钟中断.
#define OS_TICKS_PER_SEC 100u
每10ms执行的系统时钟中断相关函数如下:
void SysTick_Handler(void)
{
// 主要作用是累加OSIntNesting值, 标识当前的中断桥套层数.
// OSIntNesting > 0时表示当前处于中断处理函数中.
OSIntEnter();
// 主要作用是累加OSTime的值,并遍历任务链表,对于OSTCBDly>0的任务,对其OSTCBDly进行减1操作,并将就绪任务的优先级放入任务就绪表.
OSTimeTick();
// OSIntNesting进行减一操作, 并找出优先级最高的任务,进行一次任务调度.
OSIntExit();
}
下面对相关函数的具体内容进行进一步讲解,先来分析OSTimeTick函数,主要思路流程在代码内以注释的形式体现.
// 对于此函数的分析仅保留了相关的核心代码
void OSTimeTick (void)
{
#if OS_TIME_GET_SET_EN > 0u
OS_ENTER_CRITICAL(); /* Update the 32-bit tick counter */
// 累加OSTime值, 此值可用于标识系统运行时间.
OSTime++;
OS_EXIT_CRITICAL();
#endif
// 确保ucos已经开始调度后才执行下面的代码.
if (OSRunning == OS_TRUE) {
ptcb = OSTCBList; /* Point at first TCB in TCB list */
/*
遍历任务链表,对于OSTCBDly不为0的任务进行减一操作,如果减一后为0,判断任务是否还在因等待某个事件而挂起,
如果是,则将任务的OSTCBStatPend设置为OS_STAT_PEND_TO.否则设置为OS_STAT_PEND_OK.
判断任务是否执行了任何挂起操作,如果没有,将此优先级的任务放入就绪表.
*/
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();
}
}
}
OSTimeTick最最核心的作用就是,递减每个任务的OSTCBDly值,并将已就绪任务的优先级,放入就绪表.
OSTimeTick函数本身并不会触发任务调度,那我们的任务调度在哪里触发呢?且看下面的OSIntExit()函数的分析.
void OSIntExit (void)
{
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
/*
OSIntExi函数一般在中断中配合OSIntEnter成对出现, 其主要作用是执行完中断处理函数且无嵌套中断时,
判断一下是否有更高优先级的任务就绪了,如果有,执行任务调度.转去运行更高优先级的任务.如果没有,继续回到
之前被中断打断的那个任务去运行.
*/
if (OSRunning == OS_TRUE) {
OS_ENTER_CRITICAL();
if (OSIntNesting > 0u) { /* Prevent OSIntNesting from wrapping */
OSIntNesting--;
}
if (OSIntNesting == 0u) { /* Reschedule only if all ISRs complete ... */
if (OSLockNesting == 0u) { /* ... and not locked. */
OS_SchedNew();
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
if (OSPrioHighRdy != OSPrioCur) { /* No Ctx Sw if current task is highest rdy */
#if OS_TASK_PROFILE_EN > 0u
OSTCBHighRdy->OSTCBCtxSwCtr++; /* Inc. # of context switches to this task */
#endif
OSCtxSwCtr++; /* Keep track of the number of ctx switches */
OSIntCtxSw(); /* Perform interrupt level ctx switch */
}
}
}
OS_EXIT_CRITICAL();
}
}
总结
至此我们对于CPU产生系统时钟中断时所做的事情就比较清晰了.
总结一下就是: 遍历任务,递减其延时值.找到延时值降至0的任务将其优先级放入就绪表.在退出中断时判断是否需要进行任务 调度,并根据需要进行任务调度.
问答环节
1.问: 假设一共创建了两个任务,某一时刻都处于delay状态,这时退出系统时钟中断时如何调度.
答: UCOS默认创建了了一个最低优先级的系统任务OS_TaskIdle,这个任务永远处于就绪状态. 在没有其他任务就绪时,会调度OS_TaskIdle这个任务.
2.问: 假定有两个任务,A任务优先级为1,B任务优先级为2.简述整个软件大致运行流程:
void taskA(void *pdata)
{
while(1)
{
Led_Flip();
OSTimeDly(100);
}
}
void taskB(void *pdata)
{
while(1)
{
serial_print();
OSTimeDly(500);
}
}
答: 任务创建完成,调用OSStart后,最高优先级的任务A首先运行,LED状态翻转.然后调用OSTimeDly函数, 设置任务A的OSTCBDly值为100, 并且将任务A的优先级从就绪表移除,触发一次任务调度.此时任务B是就绪表 中优先级最高的任务.CPU调度到任务B开始运行B任务.执行serial_print()函数.然后调用OSTimeDly(500); 设置任务B的OSTCBDly值为500, 并且将任务B的优先级从就绪表移除,触发一次任务调度.此时任务A、B都不处于 就绪状态,CPU调度到最低优先级的任务OS_TaskIdle运行.在上述的任何过程中,都可能会触发系统时钟中断, 触发系统时钟中断时,OSTimeTick函数会判断任务A 任务B是否已进入任务延时,对于已经进入任务延时的任务, 递减其OSTCBDly值.如果有任务OSTCBDly减至0,就将其任务优先级放入就绪表.OSIntExit函数找到就绪表中优先级 最高的任务,进行调度.