uCOS2_CPU利用率的实现

时间:2021-06-15 16:27:12

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的利用率了。