uCOS2中的利用率
作者:JCY
此文中是对统计任务理解,若有错误之处请指出,不胜感激!
在uCOS2操作系统当中可以得到CPU的利用率,计算利用率是通过一个任务来计算的,任务的名字叫“OSTaskStat()”.如果要使用任务需要将OS_CFG.H头文件中的OS_TASK_STAT_EN宏定义为真。这样你就可以在程序中使用任务统计功能了。
如果应用程序打算使用统计任务,那么你必须在主函数当中只建立一开始任务,然后在开始任务中调用OSStatInit(),之后你就可以建立其他任务了。
我们知道如果要使用uCOS2操作系统需要在main()函数中调用OSInit()函数,先把此函数的代码复制如下:
void OSInit (void)
{
OSInitHookBegin(); /* Call port specific initialization code */
OS_InitMisc(); /* Initialize miscellaneous variables */
OS_InitRdyList(); /* Initialize the Ready List */
OS_InitTCBList(); /* Initialize the free list of OS_TCBs */
OS_InitEventList(); /* Initialize the free list of OS_EVENTs */
#if (OS_FLAG_EN > 0) && (OS_MAX_FLAGS > 0)
OS_FlagInit(); /* Initialize the event flag structures */
#endif
#if (OS_MEM_EN > 0) && (OS_MAX_MEM_PART > 0)
OS_MemInit(); /* Initialize the memory manager */
#endif
#if (OS_Q_EN > 0) && (OS_MAX_QS > 0)
OS_QInit(); /* Initialize the message queue structures */
#endif
OS_InitTaskIdle(); /* Create the Idle Task */
#if OS_TASK_STAT_EN > 0
OS_InitTaskStat(); /* Create the Statistic Task */
#endif
#if OS_TMR_EN > 0
OSTmr_Init(); /* Initialize the Timer Manager */
#endif
OSInitHookEnd(); /* Call port specific init. code */
#if OS_DEBUG_EN > 0
OSDebugInit();
#endif
}
在函数中会看到如下代码:
OS_InitTaskIdle(); /* Create the Idle Task */
#if OS_TASK_STAT_EN > 0
OS_InitTaskStat(); /* Create the Statistic Task */
这两个函数其实就是建立两个任务,一个是空闲任务,一个是任务统计任务。空闲任务是每一个uCOS2应用程序中必须要使用的,但是统计任务可以不用。
在InitTaskIdle()函数中的关键代码如下:
(void)OSTaskCreateExt(OS_TaskIdle,
(void *)0, /* No arguments passed to OS_TaskIdle() */
&OSTaskIdleStk[OS_TASK_IDLE_STK_SIZE - 1], /* Set Top-Of-Stack */
OS_TASK_IDLE_PRIO, /* Lowest priority level */
OS_TASK_IDLE_ID,
&OSTaskIdleStk[0], /* Set Bottom-Of-Stack */
OS_TASK_IDLE_STK_SIZE,
(void *)0, /* No TCB extension */
OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR);/* Enable stack checking + clear stack */
在InitTaskStat()函数中的关键代码如下:
(void)OSTaskCreateExt(OS_TaskStat,
(void *)0, /* No args passed to OS_TaskStat()*/
&OSTaskStatStk[OS_TASK_STAT_STK_SIZE - 1], /* Set Top-Of-Stack */
OS_TASK_STAT_PRIO, /* One higher than the idle task */
OS_TASK_STAT_ID,
&OSTaskStatStk[0], /* Set Bottom-Of-Stack */
OS_TASK_STAT_STK_SIZE,
(void *)0, /* No TCB extension */
OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR); /* Enable stack checking + clear */
从函数的调用参数可以知道统计任务的优先级为OS_TASK_STAT_PRIO,而空闲任务的优先级为OS_TASK_IDLE_PRIO。在Ucos_ii.h中有以下宏定义:
#define OS_TASK_STAT_PRIO (OS_LOWEST_PRIO - 1) /* Statistic task priority */
#define OS_TASK_IDLE_PRIO (OS_LOWEST_PRIO) /* IDLE task priority */
可以看到空闲任务的优先级最低,任务统计的优先级次低。在应用程序中建立的任何其他任务都会比这两个任务的优先级高。
在main函数中只要调用OSStart()函数后,程序就会执行最高优先级的任务,就是在main()函数中建立的开始任务。在此任务中回调用统计任务初始化函数OSStatInit()。这个函数的源码如下:
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0;
#endif
OSTimeDly(2); /* Synchronize with clock tick */
OS_ENTER_CRITICAL();
OSIdleCtr = 0L; /* Clear idle counter */
OS_EXIT_CRITICAL();
OSTimeDly(OS_TICKS_PER_SEC / 10); /* Determine MAX. idle counter value for 1/10 second */
OS_ENTER_CRITICAL();
OSIdleCtrMax = OSIdleCtr; /* Store maximum idle counter count in 1/10 second */
OSStatRdy = OS_TRUE;
OS_EXIT_CRITICAL();
在函数中的OSTimeDly(2);系统调用是为了,同步时钟滴答。因为在开始计算空闲计数器时,产生系统滴答的定时器计数器可能会偏离计数器设定的最大值很多。这样会造成计算出来的空闲计数器最大值误差较大。加上了此函数后,就会减小误差,因为一次滴答结束之后,只需要执行一小段的程序就要执行 OSTimeDly(OS_TICKS_PER_SEC / 10)函数了。这一段小段程序只花费了定时器计数器很少的时间,这要计算出来的空闲计数器的最大值会有很少的偏差,可以忽略不计。
OSTimeDly(OS_TICKS_PER_SEC / 10); 此函数被调用之后开始任务就会处于挂起状态。这样就会执行任务统计这个任务了,在看统计任务之前先看一下空闲任务,空闲任务的源码如下:
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0;
#endif
(void)p_arg; /* Prevent compiler warning for not using 'p_arg' */
for (;;) {
OS_ENTER_CRITICAL();
OSIdleCtr++;
OS_EXIT_CRITICAL();
OSTaskIdleHook(); /* Call user definable HOOK */
}
空闲任务其实执行的就是红色部分的区域,就是对全局变量OSIdleCtr不停的递增。而OSTaskIdleHook函数中是空函数。是为了防止OSIdleCtr可能会溢出,由用户程序在此函数中添加必要的延时。OSIdleCtr在此任务中更新,会在任务统计这任务使用。
任务统计这任务源代如下:
void OS_TaskStat (void *p_arg)
{
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0;
#endif
(void)p_arg; /* Prevent compiler warning for not using 'p_arg' */
while (OSStatRdy == OS_FALSE) {
OSTimeDly(2 * OS_TICKS_PER_SEC / 10); /* Wait until statistic task is ready */
}
OSIdleCtrMax /= 100L;
if (OSIdleCtrMax == 0L) {
OSCPUUsage = 0;
(void)OSTaskSuspend(OS_PRIO_SELF);
}
for (;;) {
OS_ENTER_CRITICAL();
OSIdleCtrRun = OSIdleCtr; /* Obtain the of the idle counter for the past second */
OSIdleCtr = 0L; /* Reset the idle counter for the next second */
OS_EXIT_CRITICAL();
OSCPUUsage = (INT8U)(100L - OSIdleCtrRun / OSIdleCtrMax);
OSTaskStatHook(); /* Invoke user definable hook */
#if (OS_TASK_STAT_STK_CHK_EN > 0) && (OS_TASK_CREATE_EXT_EN > 0)
OS_TaskStatStkChk(); /* Check the stacks for each task */
#endif
OSTimeDly(OS_TICKS_PER_SEC / 10); /* Accumulate OSIdleCtr for the next 1/10 second */
}
}
该任务基本上一直会处在挂起状态,因为它一直在对统计就绪这个全局变量OSStatRdy进行遍历,等待其为真才回执行下面的语句。它什么时候为真呐?在OSStatInit函数中有一个语句OSStatRdy = OS_TRUE;。可以看出当计算出100个滴答空闲计数器的值后并赋值给OSIdleCtrMax 变量。之后就可以执行OSIdleCtrMax /= 100L;了。这个语句是为了得到1个时钟滴答的空闲计数器最大值。然后进入任务死循环for( ; ; ),在此循环中就是一直在延时100个是时钟滴答,然后取出OSIdleCtr值,放在OSIdleCtrRun 变量中,通过OSCPUUsage = (INT8U)(100L - OSIdleCtrRun / OSIdleCtrMax);得出CPU的利用率。
在此要说明的是 OSIdleCtrMax为一个时钟滴答的空闲计数器最大值,而OSIdleCtrRun 为100个时钟滴答的空闲计数器值。 OSIdleCtrRun / OSIdleCtrMax等价于100*(一个时钟滴答的空闲计数器值/(一个时钟滴答空闲计数器的最大值)。(INT8U)(100L - OSIdleCtrRun / OSIdleCtrMax)就是CPU的利用率了。