FreeRTOS系统中CPU使用率统计方法分析
基本概念
操作系统中CPU使用率是在软件架构设计中必须要考虑的一个重要性能指标。它直接影响到程序的执行时间以及优先级更高的任务能否实时响应的问题。而CPU使用率也不能过高,避免资源浪费。
统计方法
FreeRTOS操作系统是使用任务的累计运行时间来统计每一个任务自系统开始运行到当前时刻的CPU占用时间,即该任务的CPU使用率。
可能听起来比较难以理解,比如:系统上电到当前时刻一共运行了100s,其中任务A运行了1s,任务B运行了2s,剩下的97s由空闲任务在运行,那么在10s的时间内,任务A的CPU使用率是1%,任务B的CPU使用率是2%,空闲任务的CPU使用率是97%。
下图是FreeRTOS系统CPU使用率统计示意图:
源码分析1
FreeRTOS系统中CPU使用率统计功能是一个可裁剪的功能,可通过宏configGENERATE_RUN_TIME_STATS来进行裁剪,代码如下:
FreeRTOSConfig.h
/* 使能CPU使用率统计功能 */
#define configGENERATE_RUN_TIME_STATS 0
而如果使能了CPU使用率统计功能,则在每一个任务的任务控制块中,定义了一个变量ulRunTimeCounter用于统计该任务的运行时间,代码如下:
task.c
/* 任务控制块接口体定义 */
typedef struct tskTaskControlBlock{
volatile StackType_t *pxTopOfStack;
// ...
#if( configGENERATE_RUN_TIME_STATS == 1 )
uint32_t ulRunTimeCounter; /*< Stores the amount of time the task has spent in the Running state. */
#endif
// ...
}
在进行任务上下文切换时,会将当前任务的本次运行时间(从上一次任务上下文切换开始至当前时刻)累计到当前任务的任务控制块中的ulRunTimeCounter上,任务上下文切换的代码如下:
task.c
void vTaskSwitchContext( void ){
if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE )
{
/* The scheduler is currently suspended - do not allow a context switch. */
xYieldPending = pdTRUE;
}
else
{
xYieldPending = pdFALSE;
traceTASK_SWITCHED_OUT();
/* 条件编译选项:如果使能了CPU使用率统计功能,则下面的代码用于统计当前任务的CPU占用总时间 */
#if ( configGENERATE_RUN_TIME_STATS == 1 )
{
/* **(1)获取当前时间ulTotalRunTime(CPU运行的总时间)** */
#ifdef portALT_GET_RUN_TIME_COUNTER_VALUE
portALT_GET_RUN_TIME_COUNTER_VALUE( ulTotalRunTime );
#else
ulTotalRunTime = portGET_RUN_TIME_COUNTER_VALUE();
#endif
/* **(2)将当前任务的运行时间(当前时间(ulTotalRunTime) - 上一次任务切换的时间(ulTaskSwitchedInTime))累加到当前任务控制块** */
if( ulTotalRunTime > ulTaskSwitchedInTime )
{
pxCurrentTCB->ulRunTimeCounter += ( ulTotalRunTime - ulTaskSwitchedInTime );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* **(3)更新上一次任务切换的时间ulTaskSwitchedInTime,作为下一次任务切换时计算任务运行时间的基准时刻** */
ulTaskSwitchedInTime = ulTotalRunTime;
}
#endif /* configGENERATE_RUN_TIME_STATS */
/* Check for stack overflow, if configured. */
taskCHECK_FOR_STACK_OVERFLOW();
/* Select a new task to run using either the generic C or port optimised asm code. */
taskSELECT_HIGHEST_PRIORITY_TASK();
traceTASK_SWITCHED_IN();
#if ( configUSE_NEWLIB_REENTRANT == 1 )
{
/* Switch Newlib's _impure_ptr variable to point to the _reent structure specific to this task. */
_impure_ptr = &( pxCurrentTCB->xNewLib_reent );
}
#endif /* configUSE_NEWLIB_REENTRANT */
}
}
由上面的代码可知,要实现任务的CPU使用率统计功能,还需要实现宏函数portGET_RUN_TIME_COUNTER_VALUE()用于获取当前时间,这里有两种方式:
(1)使用系统时基(一般取1ms)作为当前时间,这种方式仅用于粗略估算CPU使用率,代码如下:
FreeRTOSConfig.h
/* Run time stats gathering definitions. */
#define configGENERATE_RUN_TIME_STATS 1
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()
#define portGET_RUN_TIME_COUNTER_VALUE() xTaskGetTickCount() /* 获取系统tick */
(2)使用高精度定时器(一般精度在50us以上,即系统时钟节拍的10~20倍)作为当前时间,这种方式计算的CPU使用率相对准确,代码如下:
FreeRTOSConfig.h
/* Run time stats gathering definitions. */
#define configGENERATE_RUN_TIME_STATS 1
extern volatile uint32_t ulHighFrequencyTimerCounts; /* 在高精度定时器中断中累加 */
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() (ulHighFrequencyTimerCounts = 0ul)
#define portGET_RUN_TIME_COUNTER_VALUE() ulHighFrequencyTimerCounts
然后在定时器中断处理程序中对变量ulHighFrequencyTimerCounts进行累加,代码如下
IT_Timer.c
/* 用于统计CPU运行时间 */
volatile uint32_t ulHighFrequencyTimerCounts = 0ul;
/* 高精度定时器中断服务函数(定时周期50us) */
void TIM_IRQ_Handler(void)
{
// 判断中断标志位
ulHighFrequencyTimerCounts++;
// 清除中断标志
}
用户使用方法
用户可以通过调用 vTaskList() 和 vTaskGetRunTimeStats() 函数获取任务的相关信息和CPU使用率的相关信息,然后打印出来即可,使用代码如下:
user_monitor.c
void DumpTaskSysFree(void)
{
uint8_t CPU_RunInfo[400] = {0};
memset(CPU_RunInfo, 0, 400);
vTaskList((char *)&CPU_RunInfo);
printf("任务名 任务状态 优先级 剩余栈 任务序号\r\n ");
printf("%s\r\n", CPU_RunInfo);
memset(CPU_RunInfo,0,400);
vTaskGetRunTimeStats((char *)&CPU_RunInfo);
printf("任务名 运行计数 使用率 \r\n ");
printf("%s\r\n", CPU_RunInfo);
}
而要启用 vTaskList() 和 vTaskGetRunTimeStats() 函数,还需要在FreeRTOSConfig.h中添加以下配置
FreeRTOSConfig.h
/* 启用可视化跟踪调试 */
#define configUSE_TRACE_FACILITY 1
#define configUSE_STATS_FORMATTING_FUNCTIONS 1
源码分析2
下面一起来分析以下vTaskGetRunTimeStats() 函数源码(vTaskList()函数分析过程大致相同,这里不再赘述):
task.c
#if ( ( configGENERATE_RUN_TIME_STATS == 1 ) && ( configUSE_STATS_FORMATTING_FUNCTIONS > 0 ) )
void vTaskGetRunTimeStats( char *pcWriteBuffer )
{
TaskStatus_t *pxTaskStatusArray;
volatile UBaseType_t uxArraySize, x;
uint32_t ulTotalTime, ulStatsAsPercentage;
#if( configUSE_TRACE_FACILITY != 1 )
{
#error configUSE_TRACE_FACILITY must also be set to 1 in FreeRTOSConfig.h to use vTaskGetRunTimeStats().
}
#endif
/* 确保 write buffer中没有字符串(写入字符串结束符). */
*pcWriteBuffer = 0x00;
/* 获取当前系统任务个数 */
uxArraySize = uxCurrentNumberOfTasks;
/* 为每一个任务申请一块内存空间用于存储任务状态信息. */
pxTaskStatusArray = pvPortMalloc( uxCurrentNumberOfTasks * sizeof( TaskStatus_t ) );
if( pxTaskStatusArray != NULL )
{
/* Generate the (binary) data. */
uxArraySize = uxTaskGetSystemState( pxTaskStatusArray, uxArraySize, &ulTotalTime );
/* ulTotalTime为系统运行总时间. */
ulTotalTime /= 100UL;
/* Avoid divide by zero errors. */
if( ulTotalTime > 0 )
{
/* Create a human readable table from the binary data. */
for( x = 0; x < uxArraySize; x++ )
{
/* 计算当前任务的CPU占用率. */
ulStatsAsPercentage = pxTaskStatusArray[ x ].ulRunTimeCounter / ulTotalTime;
/* 将任务名写入到输出字符串. */
pcWriteBuffer = prvWriteNameToBuffer( pcWriteBuffer, pxTaskStatusArray[ x ].pcTaskName );
/* 将当前任务的运行时间ulRunTimeCounter和CPU占用率写入到输出字符串 */
if( ulStatsAsPercentage > 0UL )
{
#ifdef portLU_PRINTF_SPECIFIER_REQUIRED
{
sprintf( pcWriteBuffer, "\t%lu\t\t%lu%%\r\n", pxTaskStatusArray[ x ].ulRunTimeCounter, ulStatsAsPercentage );
}
#else
{
/* sizeof( int ) == sizeof( long ) so a smaller printf() library can be used. */
sprintf( pcWriteBuffer, "\t%u\t\t%u%%\r\n", ( unsigned int ) pxTaskStatusArray[ x ].ulRunTimeCounter, ( unsigned int ) ulStatsAsPercentage );
}
#endif
}
else
{
/* 如果计算的CPU占用率为0, 则用"<1%"表示 */
#ifdef portLU_PRINTF_SPECIFIER_REQUIRED
{
sprintf( pcWriteBuffer, "\t%lu\t\t<1%%\r\n", pxTaskStatusArray[ x ].ulRunTimeCounter );
}
#else
{
/* sizeof( int ) == sizeof( long ) so a smaller printf() library can be used. */
sprintf( pcWriteBuffer, "\t%u\t\t<1%%\r\n", ( unsigned int ) pxTaskStatusArray[ x ].ulRunTimeCounter );
}
#endif
}
pcWriteBuffer += strlen( pcWriteBuffer );
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 释放内存块pxTaskStatusArray . */
vPortFree( pxTaskStatusArray );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* ( ( configGENERATE_RUN_TIME_STATS == 1 ) && ( configUSE_STATS_FORMATTING_FUNCTIONS > 0 ) ) */
简单来说,vTaskGetRunTimeStats() 函数的作用就是通过当前任务的运行时间ulRunTimeCounter和系统运行总时间ulTotalTime 来计算当前任务的CPU占用率,并格式化输出。
优缺点
(1)FreeRTOS统计CPU占用时间使用的是一个32bit无符号整形来记录系统时间的,而统计时间时没有对统计时间的变量做溢出保护,当定时器精度为50us时,32bit无符号整形支持的最大计数时间为:
Tmax = 2^32 * 50us/1000100060=59.6分钟
当运行时间超过59.6分钟时,统计结果将不准确。
(2)高精度定时器频繁的进入中断也会对系统性能造成一定的影响。