在FreeRTOS中,监控任务的堆栈使用情况和运行时间统计是优化系统性能、排查资源问题的重要手段。一开始在创建任务时需要分配的栈大小,一般都是用自己的经验,但是任务栈空间的实际使用量会随着任务执行和中断处理过程上下浮动
分配栈过小会溢出,导致触发看门狗一直复位,如果分配过大则又导致资源的浪费。这时就需查看任务线程栈的大小了,使用栈情况,进行针对优化
检查任务栈最高水位
有一个函数可以查看任务剩余栈的最高水位,也就是还有多少空余的栈空间
原理是创建任务时候,栈固定的填充了0xa5(FreeRTOS的作法,这里无需自己实现),从栈底往栈顶逐个字节地判断,它们的值持续是 0xa5 就表示它是空闲的
UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask );
任务运行时、任务被切换时,都会用到栈。栈里原来值(0xa5)就会
被覆盖。逐个函数从栈的尾部判断栈的值连续为 0xa5 的个数,它就是任务运行过程中空闲内存容量的最小值。注意:假设从栈尾开始连续为 0xa5 的栈空间是 N 字节,返回值是 N/4。
返回值为这个任务栈使用的最高水位线,所谓最高水位线即自任务开始运行以来,任务所使用的栈最大时所剩余的栈大小。因此此值越大,代表任务栈剩余量越大
例如
typedef struct
{
uint32_t highWater; // the high water mark in words
uint32_t remainByte; // this is the number of byte
uint8_t remainPercent;
} TaskStackCheckInfoStruct;
/**
* ******************************************************************************
* @brief : 更新任务堆栈信息
* @param : taskName - 任务名称
* @param : taskHandle - 任务句柄
* @param : pStackInfo - 用于存储堆栈信息的结构体指针
* @param : taskStackSize - 任务堆栈大小
* @note : None
* ******************************************************************************
*/
static void taskStackInfoUpdate(char *taskName,
TaskHandle_t taskHandle,
TaskStackCheckInfoStruct *pStackInfo,
uint32_t taskStackSize)
{
// check if the parameter is available
if (NULL == taskHandle || NULL == taskName ||
NULL == pStackInfo || 0 >= taskStackSize)
{
return;
}
uint32_t highWater = uxTaskGetStackHighWaterMark(taskHandle);
// make sure that an integer overflow does not occur when calculating remain percent
if (taskStackSize > UINT32_MAX / highWater)
{
return;
}
pStackInfo->highWater = highWater;
pStackInfo->remainPercent = (highWater * 100) / taskStackSize;
pStackInfo->remainByte = highWater * 4;
LOG_INFO(MOD_TAG, "task:%s,remain percent:%u%%", taskName, pStackInfo->remainPercent);
LOG_INFO(MOD_TAG, "task:%s,remain byte:%u byte", taskName, pStackInfo->remainByte);
}
系统所有任务运行情况统计
FreeRTOS通过vTaskGetRunTimeStats
函数统计每个任务的CPU占用率,其本质是记录任务在调度器中处于运行状态的总时间。实现需要两个前提:
- 高精度定时器:统计任务运行时长的基准时钟(建议使用ESP32的CPU时钟)
- 运行时状态记录:内核在每个任务切换时记录时间戳差值
注意:如果是stm32,需要自己实现上述,具体网上有其他资料可参考,如果是esp-idf,则已经集成,打开宏定义即可使能使用
配置关键宏定义
需在menuconfig
中开启以下配置:
宏定义 | 作用 | 配置路径 |
---|---|---|
configUSE_TRACE_FACILITY | 启用内核跟踪功能 | Component config → FreeRTOS → Enable FreeRTOS trace facility |
configUSE_STATS_FORMATTING_FUNCTIONS | 启用统计格式化函数 | 同上路径下勾选该选项 |
configGENERATE_RUN_TIME_STATS | 启用运行时统计生成 | Component config → FreeRTOS → Enable FreeRTOS run time stats |
如图所示
代码实现全任务统计输出
核心API函数
/* 生成任务状态列表(含堆栈信息) */
void vTaskList(char *pcWriteBuffer);
/* 生成CPU运行时统计(百分比形式) */
void vTaskGetRunTimeStats(char *pcWriteBuffer);
统计任务实现
static void memoryInfoTaskEntry(void *pParams)
{
(void)pParams;
static uint32_t lastWakeTime;
lastWakeTime = xTaskGetTickCount();
uint8_t infoBuffer[512];
uint8_t timeBuffer[512];
for (;;)
{
memset(infoBuffer, 0, sizeof(infoBuffer));
vTaskList((char *)&infoBuffer);
ESP_LOGI(MOD_TAG, "\r\n-name- -status- -priority- -stack- -num- -core-\r\n%s", infoBuffer);
memset(timeBuffer, 0, sizeof(timeBuffer));
vTaskGetRunTimeStats((char *)&timeBuffer);
ESP_LOGI(MOD_TAG, "\r\n-name- -runTime- -percent-\r\n%s", timeBuffer);
vTaskDelayUntil(&lastWakeTime, pdMS_TO_TICKS(2000);
}
}
效果:
- 第一列,任务名称
- 第二列,任务状态
- 第三列,任务优先级(数值越大,优先级越高)
- 第四列,任务栈,数值越大,代表剩余的任务栈空间越大,单位Byte,注意此值为当前剩余栈大小,而不是峰值剩余栈大小
- 第五列,任务号,代表任务创建顺序
第六列,任务内核,代表任务所在内核ID
查询任务运行时间占用率
效果:
优化
- 堆栈优化
经验值法:初始分配时采用保守值(如4KB)
水位检测法:通过uxTaskGetStackHighWaterMark监测实际使用量 - CPU负载优化
目标值:单任务CPU占比一般不超过70%
异常排查:持续100%占用 → 任务中缺少阻塞调用(如vTaskDelay),多个任务高占比 → 考虑提升CPU主频或优化算法 - 注意事项
内存开销:启用统计功能会增加约5-10KB的ROM占用
统计间隔:建议5秒以上间隔,高频统计可能影响系统实时性
线程安全:vTaskList和vTaskGetRunTimeStats不是原子操作,建议在独立任务中调用
通过结合栈水位检测与运行时统计,可以精准把控系统资源使用情况。