1.开发背景
基于 FreeRTOS 的学习已经接近尾声,事实上看似没问题的任务可能存在分配合理的地方,某个线程任务长期占用控制器,甚至是线程任务的内存已经开始慢慢泄漏并且越界使用,如果全然不知可能导致程序崩溃。因此需要监控线程的内存和 MCU 使用情况。
事实上 FreeRTOS 开放了这样监控接口,但是需要消耗额外的资源,比较适合资源 MCU 资源丰富一点的芯片。
系统提供接口: vTaskGetRunTimeStats,参考运行时间统计 - FreeRTOS™
系统提供接口:uxTaskGetSystemState,参考uxTaskGetSystemState() - FreeRTOS™
2.开发需求
定期监控线程的 CPU 和堆栈使用情况。
3.开发环境
window10 + MDK + STM32F429 + FreeRTOS10.3.1
4.实现步骤
FreeRTOSConfig.h 配置
#define configUSE_TRACE_FACILITY (1)
#define configGENERATE_RUN_TIME_STATS (1)
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() (xTickCount = 0)
#define portGET_RUN_TIME_COUNTER_VALUE() (xTaskGetTickCount()) // 时间计数
理论上应该使用更高的时钟去监控系统的行为,这里只是要粗略估算 CPU 的使用率,所以直接使用了系统时钟,计算比较粗糙。
4.1 实现编码
#ifndef APP_MONITOR_H
#define APP_MONITOR_H
#include <stdbool.h>
#include "appTaskConfig.h"
#define MONITOR_DEFAULT_CHECK_PERIOD_MS (2000)
#define THRESHOLD_TASK_STACK_WARNING (64)
#define THRESHOLD_SYSTEM_STACK_WARNING (256)
#define THRESHOLD_RUN_TIME_PERCENT (5) // 5% ?
#define MONITOR_TASK_STACK stackMonitor // 监控线程堆栈大小
#define MONITOR_TASK_PRIORITY priorityMonitor // 监控线程优先级
/* 设置线程监控线程默认参数 */
#ifndef MONITOR_TASK_STACK
#define MONITOR_TASK_STACK (256)
#endif
#ifndef MONITOR_TASK_PRIORITY
#define MONITOR_TASK_PRIORITY (1)
#endif
void aMonitor_Init(void);
void aMonitor_SetCheckPeriod(unsigned int periodMs);
void aMonitor_Enable(bool isEnable);
void aMonitor_ShowSystemState(void); // 未实现
#endif
#include "appMonitor.h"
#include <stdio.h>
#include <string.h>
#include "appLog.h"
#include "appTaskConfig.h"
/* 任务管理结构体 */
typedef struct
{
unsigned int checkPeriodMs;
TaskHandle_t taskMonitor;
#define LIST_BUFF_DIV (4)
#define LIST_BUFF_SIZE (1024)
#define LIST_BUFF_LOG_SIZE (LIST_BUFF_SIZE / LIST_BUFF_DIV)
char pcWriteBuffer[LIST_BUFF_SIZE];
}Ctrl_t;
static Ctrl_t s_ctrl = {
.checkPeriodMs = MONITOR_DEFAULT_CHECK_PERIOD_MS,
.taskMonitor = NULL,
};
static Ctrl_t *p = &s_ctrl;
static void MonitorTask(void* pvParameters);
/**
* @brief 系统监控任务
* @param pvParameters 传入参数
* @return void
*/
static void MonitorTask(void* pvParameters)
{
vTaskDelay(1000);
/* 遍历所有存在的线程最小堆栈大小 */
UBaseType_t taskNumber = uxTaskGetNumberOfTasks();
TaskStatus_t *pxTaskStatusArray = pvPortMalloc(taskNumber * sizeof(TaskStatus_t));
uxTaskGetSystemState(pxTaskStatusArray, taskNumber, NULL);
for (int i = 0; i < taskNumber; i++)
{
Log_Info("%s Space = %d Bytes\r\n", pxTaskStatusArray[i].pcTaskName,
pxTaskStatusArray[i].usStackHighWaterMark);
}
vPortFree(pxTaskStatusArray);
Log_Info("FreeRTOS Remain Space = %d Bytes\r\n", xPortGetFreeHeapSize());
/* 监控系统内存使用情况 */
for ( ; ; )
{
/* 间隔时间查询 */
vTaskDelay(s_ctrl.checkPeriodMs / (1000 / configTICK_RATE_HZ)); // FreeRTOS 延时
/* 查询总系统最小堆栈大小 */
if (xPortGetMinimumEverFreeHeapSize() <= THRESHOLD_SYSTEM_STACK_WARNING)
{
Log_Warn("FreeRTOS Min Remain Space Warning, Space = %d Bytes\r\n",
xPortGetMinimumEverFreeHeapSize());
}
/* 遍历所有存在的线程最小堆栈大小 */
taskNumber = uxTaskGetNumberOfTasks();
pxTaskStatusArray = pvPortMalloc(taskNumber * sizeof(TaskStatus_t));
uxTaskGetSystemState(pxTaskStatusArray, taskNumber, NULL);
for (int i = 0; i < taskNumber; i++)
{
if ((pxTaskStatusArray[i].usStackHighWaterMark * 4) <= THRESHOLD_TASK_STACK_WARNING)
{
Log_Warn("%s Stack Waring, Now Space = %d Bytes\r\n",
pxTaskStatusArray[i].pcTaskName, pxTaskStatusArray[i].usStackHighWaterMark);
}
/* 线程使用率高打印 */
float usedRate = (float)pxTaskStatusArray[i].ulRunTimeCounter / xTaskGetTickCount() * 100;
if (usedRate >= THRESHOLD_RUN_TIME_PERCENT)
{
Log_Info("%s pcTaskName = %s, usedRate = %.2f%%\r\n",
__func__, pxTaskStatusArray[i].pcTaskName, usedRate);
}
}
vPortFree(pxTaskStatusArray);
#if (1)
/* 统计空闲时间 */
static int timeCountMs = 0;
timeCountMs += p->checkPeriodMs;
if (timeCountMs >= 6 * 1000) // 60 * 1000
{
timeCountMs = 0;
memset(p->pcWriteBuffer, 0, LIST_BUFF_SIZE);
vTaskGetRunTimeStats(p->pcWriteBuffer);
int offset = 0;
char sendData[LIST_BUFF_LOG_SIZE + 1] = {0};
Log_Info("\r\n\r");
Log(eLog_Info, "任务名 运行计数 使用率\r\n\r");
for (int i = 0; i < LIST_BUFF_DIV; i++)
{
memcpy(sendData, p->pcWriteBuffer + offset, LIST_BUFF_LOG_SIZE);
offset += LIST_BUFF_LOG_SIZE;
Log(eLog_Info, "%s", sendData);
}
Log(eLog_Info, "---------------------------------------------\r\n");
}
#endif
}
}
/**
* @brief 系统监控的初始化
* @param void
* @return void
*/
void aMonitor_Init(void)
{
xTaskCreate(MonitorTask, "MonitorTask", MONITOR_TASK_STACK,
NULL, MONITOR_TASK_PRIORITY, &p->taskMonitor);
}
/**
* @brief 线程监控开启和关闭
* @param isEnable 开启 = true / 关闭 = false
* @return void
*/
void aMonitor_Enable(bool isEnable)
{
if (isEnable)
{
vTaskResume(p->taskMonitor);
}
else
{
vTaskSuspend(p->taskMonitor);
}
}
/**
* @brief 设置线程监控检测周期
* @param periodMs 检测内存周期/ms
* @return void
*/
void aMonitor_SetCheckPeriod(unsigned int periodMs)
{
p->checkPeriodMs = periodMs;
}
/**
* @brief 显示系统运行状态
* @param void
* @return void
*/
void aMonitor_ShowSystemState(void)
{
}
4.2 结果显示
4.3 代码分析
代码中使用了 2 种方法获取任务的运行时间,不推荐使用 vTaskGetRunTimeStats,因为返回的数据是一大段字符串,很容易出现内存溢出,当然带来的好处就是显示清晰明了。
通过跟踪源码发现 vTaskGetRunTimeStats 实际上调用的就是 uxTaskGetSystemState ,所以直接使用 uxTaskGetSystemState 获取成员状态即可。
#if ( ( configGENERATE_RUN_TIME_STATS == 1 ) && ( configUSE_STATS_FORMATTING_FUNCTIONS > 0 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
void vTaskGetRunTimeStats( char *pcWriteBuffer )
{
TaskStatus_t *pxTaskStatusArray;
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
/*
* PLEASE NOTE:
*
* This function is provided for convenience only, and is used by many
* of the demo applications. Do not consider it to be part of the
* scheduler.
*
* vTaskGetRunTimeStats() calls uxTaskGetSystemState(), then formats part
* of the uxTaskGetSystemState() output into a human readable table that
* displays the amount of time each task has spent in the Running state
* in both absolute and percentage terms.
*
* vTaskGetRunTimeStats() has a dependency on the sprintf() C library
* function that might bloat the code size, use a lot of stack, and
* provide different results on different platforms. An alternative,
* tiny, third party, and limited functionality implementation of
* sprintf() is provided in many of the FreeRTOS/Demo sub-directories in
* a file called printf-stdarg.c (note printf-stdarg.c does not provide
* a full snprintf() implementation!).
*
* It is recommended that production systems call uxTaskGetSystemState()
* directly to get access to raw stats data, rather than indirectly
* through a call to vTaskGetRunTimeStats().
*/
/* Make sure the write buffer does not contain a string. */
*pcWriteBuffer = ( char ) 0x00;
/* Take a snapshot of the number of tasks in case it changes while this
function is executing. */
uxArraySize = uxCurrentNumberOfTasks;
/* Allocate an array index for each task. NOTE! If
configSUPPORT_DYNAMIC_ALLOCATION is set to 0 then pvPortMalloc() will
equate to NULL. */
pxTaskStatusArray = pvPortMalloc( uxCurrentNumberOfTasks * sizeof( TaskStatus_t ) ); /*lint !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack and this allocation allocates a struct that has the alignment requirements of a pointer. */
if( pxTaskStatusArray != NULL )
{
/* Generate the (binary) data. */
uxArraySize = uxTaskGetSystemState( pxTaskStatusArray, uxArraySize, &ulTotalTime );
/* For percentage calculations. */
ulTotalTime /= 100UL;
/* Avoid divide by zero errors. */
if( ulTotalTime > 0UL )
{
/* Create a human readable table from the binary data. */
for( x = 0; x < uxArraySize; x++ )
{
/* What percentage of the total run time has the task used?
This will always be rounded down to the nearest integer.
ulTotalRunTimeDiv100 has already been divided by 100. */
ulStatsAsPercentage = pxTaskStatusArray[ x ].ulRunTimeCounter / ulTotalTime;
/* Write the task name to the string, padding with
spaces so it can be printed in tabular form more
easily. */
pcWriteBuffer = prvWriteNameToBuffer( pcWriteBuffer, pxTaskStatusArray[ x ].pcTaskName );
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 ); /*lint !e586 sprintf() allowed as this is compiled with many compilers and this is a utility function only - not part of the core kernel implementation. */
}
#endif
}
else
{
/* If the percentage is zero here then the task has
consumed less than 1% of the total run time. */
#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 ); /*lint !e586 sprintf() allowed as this is compiled with many compilers and this is a utility function only - not part of the core kernel implementation. */
}
#endif
}
pcWriteBuffer += strlen( pcWriteBuffer ); /*lint !e9016 Pointer arithmetic ok on char pointers especially as in this case where it best denotes the intent of the code. */
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* Free the array again. NOTE! If configSUPPORT_DYNAMIC_ALLOCATION
is 0 then vPortFree() will be #defined to nothing. */
vPortFree( pxTaskStatusArray );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}