
🎬 渡水无言 :个人主页渡水无言
❄专栏传送门 : 《linux专栏》《嵌入式linux驱动开发》《linux系统移植专栏》
❄专栏传送门 : 《freertos专栏》 《STM32 HAL库专栏》《linux裸机开发专栏》
⭐️流水不争先,争的是滔滔不绝
📚博主简介:第二十届中国研究生电子设计竞赛全国二等奖 |国家奖学金 | 省级三好学生
| 省级优秀毕业生获得者 | csdn新星杯TOP18 | 半导纵横专栏博主 | 211在读研究生
在这里主要分享自己学习的linux嵌入式领域知识;有分享错误或者不足的地方欢迎大佬指导,也欢迎各位大佬互相三连
文章目录
前言
在上篇中,我们完成了 MonitorTask 的基础搭建:
实现了 1 秒固定周期调度的监控框架;
设计了任务心跳机制,检测各业务任务是否卡死;
完成了任务信息注册,为后续资源监控铺垫。
本篇将在此基础上,新增三大核心监控功能,完善整个监控体系。
一、MonitorTask 完整运行时序

相比上篇的基础时序,新增了 3 个核心步骤:
堆内存监控:每秒检查当前剩余堆内存、历史最低空闲堆内存,预警内存泄漏;
任务栈监控:每秒遍历所有注册任务,计算栈使用率,分级输出预警日志;
任务列表输出:每 10 秒打印一次系统所有任务的状态、优先级、栈使用情况,方便调试。
二、堆内存监控
在使用 pvPortMalloc() 等动态内存分配的 FreeRTOS 系统中,堆内存监控至关重要 ------ 内存泄漏、内存碎片,都会导致系统最终耗尽内存(OOM),引发崩溃。
2.1、核心 API 说明
FreeRTOS 提供了两个关键 API,用于获取堆内存信息:
xPortGetFreeHeapSize():获取当前剩余堆内存(单位:字节);
xPortGetMinimumEverFreeHeapSize():获取系统运行以来历史最低剩余堆内存。
2.2、堆内存监控实现代码
cpp
/**
* @brief 堆内存监控
* @details 打印当前剩余堆内存、历史最低剩余堆内存,预警内存泄漏风险
*/
static void monitor_heap(void)
{
#if (configSUPPORT_DYNAMIC_ALLOCATION == 1) // 仅当开启动态内存分配时生效
size_t free_heap = xPortGetFreeHeapSize(); // 当前剩余堆内存
size_t min_heap = xPortGetMinimumEverFreeHeapSize(); // 历史最低堆内存
// 打印堆内存信息
LOGINFO_T("MON", "[HEAP] 空闲堆内存: %u 字节 | 历史最低: %u 字节",
(unsigned int)free_heap, (unsigned int)min_heap);
#else
// 未开启动态内存分配,打印提示
LOGINFO_T("MON", "[HEAP] 动态内存分配已关闭,无需监控");
#endif
}
2.3、日志输出解读与风险预警
示例输出:
cpp
[HEAP] 空闲堆内存: 7920 字节 | 历史最低: 180 字节
当前空闲堆内存 7920 字节:系统当前内存压力较小;
历史最低 180 字节:系统运行以来,曾接近耗尽堆内存,存在内存泄漏或内存分配不合理的风险;
预警判断:若历史最低堆内存持续降低,说明存在内存泄漏(不断申请内存未释放),需排查 pvPortMalloc() 的使用。
三、任务栈监控
每个 FreeRTOS 任务都有独立的栈空间,若栈空间被用尽,会引发栈溢出(Stack Overflow),破坏内存数据,导致系统崩溃、行为异常。栈监控的核心是 "高水位标记(HWM)"。
3.1、 核心 API 说明
FreeRTOS 提供两个 API 获取栈高水位标记:
uxTaskGetStackHighWaterMark(hTask):获取任务栈的高水位标记(单位:字);
uxTaskGetStackHighWaterMark2(hTask):兼容新版本 FreeRTOS,功能与前者一致。
高水位标记(HWM)含义
任务启动以来,栈剩余空间的最小值------HWM 越小,说明任务栈使用越深入,溢出风险越高。
3.2、栈使用率计算方法
栈使用率 = (分配的栈大小 - HWM) / 分配的栈大小 × 100%
cpp
// 示例:分配栈大小 256 字,HWM 40 字
uint16_t stack_total = 256; // 分配的栈大小(单位:字)
uint16_t stack_hwm = 40; // 高水位标记(单位:字)
uint8_t min_free_pct = (stack_hwm * 100) / stack_total; // 剩余栈比例(15%)
uint8_t used_pct = 100 - min_free_pct; // 栈使用率(85%)
3.3、栈监控实现代码
根据栈剩余比例,分级输出日志(ERROR/WARN/INFO),直观预警溢出风险:
剩余比例 **< 10%**(使用率 > 90%):输出 ERROR 级日志,系统危险。
剩余比例 **< 20%**(使用率 > 80%):输出 WARN 级日志,建议关注。
其余:输出 INFO 正常信息,说明任务栈设计合理。
cpp
/**
* @brief 任务栈使用率监控
* @details 遍历所有注册任务,计算栈使用率,分级输出预警日志
*/
static void monitor_stack_pct(void)
{
// 预警阈值(可配置)
#define MON_ERR_MIN_FREE_PCT 10 // 剩余 <10% → ERROR(危险)
#define MON_WARN_MIN_FREE_PCT 20 // 剩余 <20% → WARN(关注)
for (uint32_t i = 0; i < s_task_count; i++) {
MonTaskInfo_t *task = &s_tasks[i];
if (task->h == NULL) continue; // 跳过无效任务句柄
// 1. 获取栈高水位标记(HWM)
uint16_t stack_hwm = (uint16_t)uxTaskGetStackHighWaterMark(task->h);
// 2. 计算剩余栈比例
uint8_t min_free_pct = (stack_hwm * 100) / task->stack_words;
// 3. 分级输出日志
if (min_free_pct < MON_ERR_MIN_FREE_PCT) {
// 剩余栈 <10% → 危险,栈溢出风险极高
LOGERROR_T("MON", "[STACK ERROR] %s: 剩余栈%u%% (总%u字, HWM%u字)",
task->alias, min_free_pct, task->stack_words, stack_hwm);
} else if (min_free_pct < MON_WARN_MIN_FREE_PCT) {
// 剩余栈 <20% → 需关注,建议增大栈空间
LOGWARN_T("MON", "[STACK WARN] %s: 剩余栈%u%% (总%u字, HWM%u字)",
task->alias, min_free_pct, task->stack_words, stack_hwm);
} else {
// 剩余栈 ≥20% → 正常
LOGINFO_T("MON", "[STACK OK] %s: 剩余栈%u%% (总%u字, HWM%u字)",
task->alias, min_free_pct, task->stack_words, stack_hwm);
}
}
}
四、任务列表输出
在 RTOS 系统中,每个任务都是独立运行的执行单元,但在运行过程中,我们往往难以直观了解:
系统当前存在哪些任务?
每个任务处于什么状态(运行 / 阻塞 / 挂起等)?
每个任务的优先级、栈使用量是多少?
哪些任务在耗资源、可能导致系统异常?
4.1、核心 API 说明
vTaskList():FreeRTOS 自带 API,用于获取所有任务的状态信息,输出格式固定,需开启相关配置:
cpp
// FreeRTOSConfig.h 需开启以下两个宏
#define configUSE_TRACE_FACILITY 1
#define configUSE_STATS_FORMATTING_FUNCTIONS 1
4.2、任务列表监控实现代码
cpp
/**
* @brief 打印系统任务列表
* @details 每10秒执行一次,打印所有任务的状态、优先级、栈使用情况
*/
static void monitor_task_list(void)
{
#if (configUSE_TRACE_FACILITY == 1) && (configUSE_STATS_FORMATTING_FUNCTIONS == 1)
static char task_list_buf[128] = {0}; // 任务列表缓存
memset(task_list_buf, 0, sizeof(task_list_buf));
// 获取任务列表并打印
vTaskList(task_list_buf);
LOGINFO_T("MON", "----- 系统任务列表 BEGIN #%u -----", s_mon_seq);
LOGINFO_T("MON", "%s", task_list_buf);
LOGINFO_T("MON", "----- 系统任务列表 END #%u -----\n", s_mon_seq);
#else
// 未开启配置,打印提示
LOGWARN_T("MON", "[TASK LIST] 未开启配置(configUSE_TRACE_FACILITY),无法打印");
#endif
}
4.3、任务列表输出解读
输出格式及含义:
| Task Name(任务名) | State(状态) | Prio(优先级) | Stack(剩余栈) | Num(任务 ID) |
|---|---|---|---|---|
| IDLE | R | 0 | 130 | 3 |
| CONTROL | B | 2 | 64 | 1 |
| COMM | S | 1 | 68 | 2 |
| MONITOR | B | 1 | 72 | 4 |
状态(State):R= 运行中,B= 阻塞中,S= 挂起中。
优先级(Prio):数值越大,优先级越高,越先获取 CPU 时间片。
剩余栈(Stack):当前任务剩余栈空间(单位:字)。
任务 ID(Num):系统分配的任务内部编号,唯一标识任务。
总结
本期博客作为 MonitorTask系统监控的进阶篇,完成了三大核心监控功能的实现:
堆内存监控:通过 FreeRTOS 自带 API,实时监测堆内存使用情况,预警内存泄漏和 OOM 风险。
任务栈监控:基于栈高水位标记,计算栈使用率,分级输出预警日志,防止栈溢出。
任务列表输出:每 10 秒打印一次系统任务状态,为调试提供便利,快速定位任务异常。
结合上篇的心跳检测和任务注册,MonitorTask 形成了一套完整的系统监控体系,覆盖了嵌入式系统最常见的运行时风险(任务卡死、内存泄漏、栈溢出)。