MonitorTask 系统监控任务(下篇)---完善堆内存 、任务栈监控

🎬 渡水无言个人主页渡水无言

专栏传送门 : 《linux专栏》《嵌入式linux驱动开发》《linux系统移植专栏》

专栏传送门 : 《freertos专栏》 《STM32 HAL库专栏》《linux裸机开发专栏

专栏传送门《产品测评专栏》《Ai智能体专栏

⭐️流水不争先,争的是滔滔不绝

📚博主简介:第二十届中国研究生电子设计竞赛全国二等奖 |国家奖学金 | 省级三好学生

| 省级优秀毕业生获得者 | 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 形成了一套完整的系统监控体系,覆盖了嵌入式系统最常见的运行时风险(任务卡死、内存泄漏、栈溢出)。

相关推荐
代码又报错la1 小时前
5、电源保护板
单片机·嵌入式硬件
leo__52010 小时前
STM32 MAX30102 心率血氧测量代码
stm32·单片机·嵌入式硬件
yuan1999713 小时前
STM32 IAP 电量计源码
stm32·单片机·嵌入式硬件
学不懂飞行器14 小时前
从小白到国奖:全国大学生电子设计竞赛(电赛)高质量备赛全攻略
stm32·单片机·嵌入式硬件
念恒1230616 小时前
STM(GPIO)上篇
stm32·单片机·嵌入式硬件
时空自由民.17 小时前
嵌入式MCU+RTOS软件框架设计方案
单片机·嵌入式硬件
振南的单片机世界20 小时前
全双工vs半双工vs单工:电话、对讲机、广播
stm32·单片机·嵌入式硬件
笨笨饿21 小时前
#72_聊聊I2C以及他们的变体
linux·c语言·网络·stm32·单片机·算法·个人开发
CHANG_THE_WORLD21 小时前
从0到1 编写HexDump工具
单片机·嵌入式硬件