在做产品开发时,我们都会接触到一个概念,那就是余量。对于硬件,比如某电容耐压至少需要15V,那么根据经验和设计规则,可能会留有一定的余量,最后选型耐压值为16V的电容。硬件如此,软件呢?更需要按照这种思路去设计,相比硬件,软件更无形,所以留有余量是衡量软件稳定性必须考量的一个关键点。
根据在整车软件设计的经验,CPU的负载率需要控制在85%以内,堆栈使用率控制在80%,对于外部通信,CAN的通信负载率需要控制在40%以内,最高不超过45%(硬件设计达到国际一流车企标准的前提下)。
OK,言归正传。我们在进行嵌入式软件设计时,如何去统计CPU负载率和任务堆栈使用情况呢?ucosII系统为开发测试提供了良好的接口,可以完成上述两项功能,如果是使用其他RTOS系统,如freeRTOS系统,没有提供类似接口,可以参照这种做法去自行实现。
下面列举出和使用率、任务堆栈统计相关的函数:
①OSInit(); //OS系统初始化
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 > 0u) && (OS_MAX_FLAGS > 0u)
OS_FlagInit(); /* Initialize the event flag structures */
#endif
#if (OS_MEM_EN > 0u) && (OS_MAX_MEM_PART > 0u)
OS_MemInit(); /* Initialize the memory manager */
#endif
#if (OS_Q_EN > 0u) && (OS_MAX_QS > 0u)
OS_QInit(); /* Initialize the message queue structures */
#endif
OS_InitTaskIdle(); /* Create the Idle Task */
#if OS_TASK_STAT_EN > 0u
OS_InitTaskStat(); /* Create the Statistic Task */
#endif
#if OS_TMR_EN > 0u
OSTmr_Init(); /* Initialize the Timer Manager */
#endif
OSInitHookEnd(); /* Call port specific init. code */
#if OS_DEBUG_EN > 0u
OSDebugInit();
#endif
}
其中,和我们话题相关的两条语句:
OS_InitTaskIdle(); /* Create the Idle Task */
OS_InitTaskStat(); /* Create the Statistic Task */
空闲任务和统计任务的创建,里面就是创建了两个任务,这里就不过多粘贴代码,对应任务名称:OS_TaskIdle和OS_TaskStat。分别看下这两个任务:
②OS_TaskIdle()
void OS_TaskIdle (void *p_arg)
{
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
p_arg = p_arg; /* Prevent compiler warning for not using 'p_arg' */
for (;;) {
OS_ENTER_CRITICAL();
OSIdleCtr++;
OS_EXIT_CRITICAL();
OSTaskIdleHook(); /* Call user definable HOOK */
}
}
空闲任务顾名思义,当系统没有其他任务需要执行的时候,会一直执行空闲任务,且每次执行时会对OSIdleCtr进行加1操作,这个变量后面会被用来计算CPU的使用率。在设置空闲任务时,将其优先级设置为次低,及LOW_PRIO-1,所以只要有其他任务需要执行,就轮不上这个任务,轮上这个任务执行,说明系统空闲了。
OSTaskIdleHook()函数,专业名称叫钩子函数,里面是空的,留有接口可以在应用层来实现一些功能。这样写法比较明显,容易被追踪到。在其他系统里,这种功能大多使用函数指针来实现回调。
③OS_TaskStat()
#if OS_TASK_STAT_EN > 0u
void OS_TaskStat (void *p_arg)
{
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
p_arg = p_arg; /* Prevent compiler warning for not using 'p_arg' */
while (OSStatRdy == OS_FALSE) {
OSTimeDly(2u * OS_TICKS_PER_SEC / 10u); /* Wait until statistic task is ready */
}
OSIdleCtrMax /= 100uL;
if (OSIdleCtrMax == 0uL) {
OSCPUUsage = 0u;
#if OS_TASK_SUSPEND_EN > 0u
(void)OSTaskSuspend(OS_PRIO_SELF);
#else
for (;;) {
OSTimeDly(OS_TICKS_PER_SEC);
}
#endif
}
OS_ENTER_CRITICAL();
OSIdleCtr = OSIdleCtrMax * 100uL; /* Set initial CPU usage as 0% */
OS_EXIT_CRITICAL();
for (;;) {
OS_ENTER_CRITICAL();
OSIdleCtrRun = OSIdleCtr; /* Obtain the of the idle counter for the past second */
OSIdleCtr = 0uL; /* Reset the idle counter for the next second */
OS_EXIT_CRITICAL();
OSCPUUsage = (INT8U)(100uL - OSIdleCtrRun / OSIdleCtrMax);
OSTaskStatHook(); /* Invoke user definable hook */
#if (OS_TASK_STAT_STK_CHK_EN > 0u) && (OS_TASK_CREATE_EXT_EN > 0u)
OS_TaskStatStkChk(); /* Check the stacks for each task */
#endif
OSTimeDly(OS_TICKS_PER_SEC / 10u); /* Accumulate OSIdleCtr for the next 1/10 second */
}
}
#endif
根据宏定义,缩减之后关键代码如下:
void OS_TaskStat (void *p_arg)
{
while (OSStatRdy == OS_FALSE) {
OSTimeDly(2u * OS_TICKS_PER_SEC / 10u); /* Wait until statistic task is ready */
}
for (;;) {
OS_ENTER_CRITICAL();
OSIdleCtrRun = OSIdleCtr; /* Obtain the of the idle counter for the past second */
OSIdleCtr = 0uL; /* Reset the idle counter for the next second */
OS_EXIT_CRITICAL();
OSCPUUsage = (INT8U)(100uL - OSIdleCtrRun / OSIdleCtrMax);
OSTaskStatHook(); /* Invoke user definable hook */
OS_TaskStatStkChk(); /* Check the stacks for each task */
OSTimeDly(OS_TICKS_PER_SEC / 10u); /* Accumulate OSIdleCtr for the next 1/10 second */
}
}
#endif
这个函数就很明显,计算了CPU使用率OSCPUUsage。
任务栈空间使用的统计,在函数OS_TaskStatStkChk();中,在此函数中会对所有任务进行遍历,并在OS_TaskStatStkChk()下一级函数OSTaskStkChk()统计出每个栈空间free_size的大小,然后用total_size减去free_size就是已使用的空间。关键统计部分如下:
while (*pchk++ == (OS_STK)0) { /* Compute the number of zero entries on the stk */
nfree++;
}
p_stk_data->OSFree = nfree; /* Store number of free entries on the stk */
p_stk_data->OSUsed = size - nfree; /* Compute number of entries used on the stk */
很显然,在while中从栈的基地址往栈底进行统计,如果为0则认为是free的空间,直到发现非0空间,才停止统计。然后用free和总的size来计算OSUsed已使用空间。这个统计方法比较实用,但如果很较真的话,这个统计有点弊端,假如一个数组被压如栈中,buf[16]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2};按照存储规则,如下图,统计出来会有误差,所以这个统计只能作为参考值,在参考值之外需要留有一定余量。并且在统计过程中,需要对各种可能发生的情况进行测试,才能得到比较接近的值。
④OSStatInit ()
void OSStatInit (void)
{
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
OSTimeDly(2u); /* Synchronize with clock tick */
OS_ENTER_CRITICAL();
OSIdleCtr = 0uL; /* Clear idle counter */
OS_EXIT_CRITICAL();
OSTimeDly(OS_TICKS_PER_SEC / 10u); /* 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();
}
#endif
最后,如何开启这个统计功能呢?那就需要调用该初始化函数了。好了,到此为止,CPU使用率统计和任务栈使用统计就能够实现了。
在代码正式上线时,需要把该任务关闭,即将相关的宏定义定义为0即可,以节省资源。
转载请注明出处,如有错误,欢迎指正!