当前位置: 首页 > web >正文

【Zephyr炸裂知识系列】11_手撸内存泄露监测算法

文章目录

  • 前言
  • 一、监测核心算法架构
      • 1.1:算法架构图
      • 1.2:关键设计理念
  • 二、链表记录算法实现原理
      • 2.1:宏拦截机制
      • 2.2 内存记录结构
      • 2.3 核心算法流程
  • 三、泄露检测算法原理
      • 3.1 泄露判定标准
      • 3.2 检测算法实现
  • 四、实际内存泄露监测案例测试
      • 4.1:内存申请
      • 4.2:内存释放
      • 4.3:制造内存泄露
      • 4.4:手动打印内存信息
      • 4.5:制造重复释放
  • 五、算法优势与特点
    • 5.1 技术优势
    • 5.2 适用场景
  • 总结

前言

在嵌入式系统开发中,内存泄漏是一个常见且难以调试的问题。特别是在资源受限的物联网或单片机设备中,即使是微小的内存泄漏,长期运行后也可能导致系统崩溃。世面上有Valgrind、AddressSanitizer等强大的工具,但对于资源受限的嵌入式设备,这些工具往往过于庞大或无法使用,而手动检查又效率低下。
为此,我们本文将介绍一种轻量级的内存泄露检测算法,专为RTOS环境设计,它能够在资源受限的环境中实时监测内存使用情况,准确识别内存泄漏和错误操作。本文将详细介绍该检测器的核心架构、使用方法和实际测试效果。


一、监测核心算法架构

1.1:算法架构图

					┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐│   应用程序代码   │     │   内存检测拦截宏   │   │  拦截宏内重新调用  ││                 │    │                 │    │                 ││ malloc()        │───▶│    ml_malloc()  │───▶│ malloc()        ││ free()          │───▶│    ml_free()    │───▶│ free()          ││ calloc()        │───▶│    ml_calloc()  │───▶│ calloc()        ││ realloc()       │───▶│    ml_realloc() │───▶│ realloc()       │└─────────────────┘    └─────────────────┘    └─────────────────┘│▼┌─────────────────┐│   内存记录链表    ││ - 分配地址       ││ - 分配大小       ││ - 文件位置       ││ - 行号信息       ││ - 时间戳         │└─────────────────┘

1.2:关键设计理念

  • 拦截层:通过宏替换拦截所有内存操作函数
  • 记录层:维护分配记录链表,保存完整的上下文信息
  • 分析层:实时分析内存使用情况,检测泄漏和错误
  • 报告层:提供多种输出方式(日志、文件、统计信息)

二、链表记录算法实现原理

2.1:宏拦截机制

// 条件编译控制,避免循环引用
#ifdef MEMORY_LEAK_USER_OPERATE#define malloc(size) ml_malloc(size, __FILE__, __LINE__)#define free(ptr) ml_free(ptr, __FILE__, __LINE__)#define calloc(num, size) ml_calloc(num, size, __FILE__, __LINE__)#define realloc(ptr, size) ml_realloc(ptr, size, __FILE__, __LINE__)
#endif

关键点

  • 使用 __FILE____LINE__ 宏获取调用位置
  • 通过条件编译避免在检测器内部形成循环
  • 保持与标准库函数相同的接口

2.2 内存记录结构

typedef struct {void* address;           // 内存地址uint32_t size;           // 分配大小uint32_t timestamp;      // 分配时间戳uint32_t line;           // 源代码行号char file_name[32];      // 文件名(仅文件名,不包含路径)char thread_name[25];    // 线程名称leak_status_t status;    // 泄露状态
} memory_info_t;typedef struct memory_node {memory_info_t mem_info;struct memory_node* next;
} memory_node_t;

内存优化策略

  • 文件名只存储文件名部分,节省内存
  • 使用链表结构,支持动态增长
  • 每个节点开销约71字节

2.3 核心算法流程

分配跟踪算法

void* ml_malloc(size_t size, const char* file, uint32_t line) {// 1. 调用C语言真实mallocvoid* ptr = malloc(size);if (ptr != NULL) {// 2. 记录分配信息ml_add_info(ptr, k_uptime_get_32(), size, file, line, k_thread_name_get(k_current_get()), LEAK_OK);}return ptr;
}

释放跟踪算法

void ml_free(void* ptr, const char* file, uint32_t line) {// 1. 从记录中查找并移除memory_node_t* node = ml_find_and_remove(ptr);if (node == NULL) {// 2. 检测重复释放ml_add_info(ptr, k_uptime_get_32(), 0, file, line,k_thread_name_get(k_current_get()), LEAK_DOUBLE_FREE);} else {// 3. 正常释放,更新统计g_stats.total_deallocations++;g_stats.current_allocations--;g_stats.total_memory_used -= node->mem_info.size;free(node);}// 4. 调用真实freefree(ptr);
}

三、泄露检测算法原理

3.1 泄露判定标准

typedef enum {LEAK_OK = 0,           // 正常LEAK_MEMORY_LEAK,      // 内存泄露LEAK_SUSPICIOUS,       // 可疑分配LEAK_DOUBLE_FREE,      // 重复释放LEAK_FREE_NULL         // 释放空指针
} leak_status_t;

泄露判定逻辑

  • 内存泄露:分配后超过 MIN_LIFETIME_MS 仍未释放
  • 可疑分配:分配时间超过阈值但可能正常(如全局变量)
  • 重复释放:对同一地址多次调用 free()
  • 释放空指针:对 NULL 调用 free()

3.2 检测算法实现

void ml_check_leaks(void) {if (lifetime > CONFIG_MEMORY_LEAK_DETECTOR_MIN_LIFETIME_MS) {if (lifetime > CONFIG_MEMORY_LEAK_DETECTOR_MIN_LIFETIME_MS * 3) {// 超过3倍时间阈值,判定为泄露current->mem_info.status = LEAK_MEMORY_LEAK;g_stats.leak_count++;} else {// 超过阈值但未超过3倍时间,判定为可疑current->mem_info.status = LEAK_SUSPICIOUS;g_stats.suspicious_count++;}}
}

四、实际内存泄露监测案例测试

必须操作:包含宏代替文件

// 在main.c中的集成示例
#include <stdlib.h>
#ifdef CONFIG_MEMORY_LEAK_DETECTOR#define MEMORY_LEAK_USER_OPERATE#include <memory_leak_detector.h>
#endif

4.1:内存申请

申请程序:

	LOG_INF("--- 测试1:正常内存操作 ---");void* normal_ptr = malloc(100, __FILE__, __LINE__);LOG_INF("正常分配: %p", normal_ptr);

插桩反馈:

 <inf> main: --- 测试1:正常内操作 ---
[MEMORY_LEAK] malloc(100) = 0xc3225d8 at WEST_TOPDIR/projects/watch_demo/w30_app/main.c:122 (thread: Thread_0x809eb0)<inf> main: 正常分配: 0xc3225d8

说明:

  • 成功拦截malloc调用
  • 正确记录分配地址、大小、文件位置、行号
  • 线程信息正确显示

4.2:内存释放

释放程序:

	free(normal_ptr, __FILE__, __LINE__);LOG_INF("正常释放");

插桩反馈

[MEMORY_LEAK] free(0xc3225d8) at WEST_TOPDIR/projects/watch_demo/w30_app/main.c:124
<inf> main: 正常释放

说明:

  • 成功拦截free调用
  • 正确记录释放地址、大小、文件位置、行号
  • 正确移除分配记录

4.3:制造内存泄露

制造泄露程序

LOG_INF("--- 测试2:制造内存泄露 ---");
static void create_memory_leaks(void)
{// 故意泄露1:分配内存但不释放void* leak1 = malloc(100);LOG_INF("分配了100字节内存但不释放: %p", leak1);// 故意泄露2:分配更多内存但不释放void* leak2 = calloc(10, 50);LOG_INF("分配了500字节内存但不释放: %p", leak2);// 故意泄露3:重新分配内存但不释放void* leak3 = malloc(200);void* leak3_new = realloc(leak3, 300);LOG_INF("重新分配了300字节内存但不释放: %p", leak3_new);// 正常的内存操作(对比)void* normal = malloc(150);LOG_INF("正常分配150字节内存: %p", normal);free(normal);LOG_INF("正常释放内存");
}

插桩反馈

<inf> main: --- 测2:制造内存泄露 ---
MEMORY_LEAK] malloc(100) = 0xc3225d8 at WEST_TOPDIR/projects/watch_demo/w30_app/main.c:28 (thread: Thread_0x809eb0)
<inf> main: 分配了100字节内存但不释放: 0xc3225d8
[MEMORY_LEAK] malloc(500) = 0xc342908 at WEST_TOPDIR/projects/watch_demo/w30_app/main.c:32 (thread: Thread_0x809eb0)
<inf> main: 分配了500节内存但不释放: 0xc342908
[MEMORY_LEAK] malloc(200) = 0xc3226f8 at WEST_TOPDIR/projects/watch_demo/w30_app/main.c:36 (thread: Thread_0x809eb0)
[MEMORY_LEAK] malloc(300) = 0xc342b08 at WEST_TOPDIR/projects/watch_demo/w30_app/main.c:37 (thread: Thread_0x809eb0)
<inf> main: 重新分配了300字节内存但不释放: 0xc342b08
[MEMORY_LEAK] malloc(150) = 0xc322750 at WEST_TOPDIR/projects/watch_demo/w30_app/main.c:41 (thread: Thread_0x809eb0)
<inf> main: 正常分配150字节内存: 0xc322750
[MEMORY_LEAK] free(0xc322750) at WEST_TOPDIR/projects/watch_demo/w30_app/main.c:43

说明:

  • 准确检测到3个故意制造的内存泄漏
  • 显示完整的泄漏信息

4.4:手动打印内存信息

信息打印

static void test_memory_stats(void)
{// 先检查泄露,更新统计信息ml_check_leaks();// 然后获取最新的统计信息memory_stats_t stats;ml_get_stats(&stats);LOG_INF("=== 内存统计信息 ===");LOG_INF("总分配次数: %d", stats.total_allocations);LOG_INF("总释放次数: %d", stats.total_deallocations);LOG_INF("当前活跃分配: %d", stats.current_allocations);LOG_INF("当前使用内存总量: %d", stats.total_memory_used);LOG_INF("峰值内存使用: %d", stats.peak_memory_used);LOG_INF("泄露数量: %d", stats.leak_count);LOG_INF("可疑数量: %d", stats.suspicious_count);LOG_INF("错误数量: %d", stats.error_count);LOG_INF("==================");
}

统计反馈

 <inf> main: === 内存统计信息 ===
<inf> main: 总分配次数: 6
<inf> main: 总释放次数: 3<inf> main: 当前活跃分配: 3
<inf> main: 当前使用内存总量: 900
<inf> main: 峰值内存使用: 1050
<inf> main: 泄露数量: 3
<inf> main: 可疑数量: 0
<inf> main: 错误数量: 0
<inf> main: ==================

说明:

  • 统计数字准确反映内存使用情况
  • 峰值内存计算正确(100+500+300+150=1050字节)
  • 泄漏和错误计数准确

4.5:制造重复释放

制造重复释放程序

static void create_double_free_error(void)
{void* ptr = malloc(100);LOG_INF("分配内存用于测试重复释放: %p", ptr);// 第一次释放(正常)free(ptr);LOG_INF("第一次释放内存");// 第二次释放(错误!)free(ptr);LOG_INF("第二次释放内存(这会导致错误)");
}

插桩反馈

<inf> main: --- 测试5:重复释错误 ---
MEMORY_LEAK] malloc(100) = 0xc322768 at WEST_TOPDIR/projects/watch_demo/w30_app/main.c:52 (thread: Thread_0x809eb0)
<inf> main: 分配内存用于测试重复释放: 0xc322768
[MEMORY_LEAK] free(0xc322768) at WEST_TOPDIR/projects/watch_demo/w30_app/main.c:56
<inf> main: 第一次释放内存
[MEMORY_LEAK] malloc(20) = 0xc322768 at WEST_TOPDIR/projects/watch_demo/w30_app/main.c:60 (thread: Thread_0x809eb0)
Warning !!! ERROR: LEAK_FREE_NULL
<inf> main: 第二次释放内存(会导致错误)

说明:

  • 重复释放检测正确
  • 警告输出正确:Warning !!! ERROR: LEAK_FREE_NULL

五、算法优势与特点

5.1 技术优势

  1. 轻量级设计:每个分配仅需71字节开销
  2. 零侵入性:通过宏拦截,无需修改应用代码
  3. 实时检测:支持运行时泄露检测和报告
  4. 线程安全:支持多线程环境
  5. 可配置性:通过Kconfig灵活配置参数

5.2 适用场景

  • 嵌入式系统:资源受限的RTOS环境
  • 实时应用:需要低延迟的内存监控
  • 开发调试:快速定位内存问题
  • 生产监控:长期运行的内存健康监控

总结

本文介绍的小型内存泄露检测算法具有以下特点:

  1. 算法简洁:核心逻辑清晰,易于理解和维护
  2. 资源高效:内存开销小,适合嵌入式环境
  3. 功能完整:支持多种内存错误检测
  4. 易于集成:与现有代码无缝集成
  5. 可扩展性:支持自定义配置和扩展

这种轻量级算法为嵌入式系统提供了一种实用的内存监控解决方案,填补了大型工具无法使用的空白,是嵌入式开发者的有力工具。
详情程序请私信获取!!!!
详情程序请私信获取!!!!
详情程序请私信获取!!!!

http://www.xdnf.cn/news/19443.html

相关文章:

  • Pomian语言处理器研发笔记(二):使用组合模式定义表示程序结构的语法树
  • Tiptrans转运 | 免费5国转运地址
  • Web网络开发 -- jQuery框架
  • REST-assured 接口测试编写指南
  • 【Canvas与戳记】蓝底黄面十六角Premium Quality戳记
  • 开发环境全面配置指南:语言环境与数据库工具
  • 基于单片机音乐喷泉/音乐流水灯/音乐播放器设计
  • 规律作息 + 养成好的习惯 + 考研倒计时 111 天 + 线面积分入门 1 下半部分
  • 【LeetCode - 每日1题】鲜花游戏
  • 2025年- H101-Lc209--1979.找出数组的最大公约数(gcd最大公约数)--Java版
  • 【物联网】MQTT(Message Queuing Telemetry Transport)是什么?
  • 深入解析 dex2oat:vdex、cdex、dex 格式转换全流程实战
  • RK3576开发板串口配置及使用
  • 使用 SVM(支持向量机)进行图像分类:从读取图像到训练与分类的完整流程
  • 深入解析Nginx常见模块2
  • 【SoC】【W800】基于W800的PWM实现
  • python pyqt5开发DoIP上位机【源码】
  • 合集:搭建wiki知识库
  • C++广度优先搜索
  • React Native基本用法
  • 从支付工具到收益资产:稳定币在 Berachain 上的二次进化
  • 四、GC 垃圾回收(二)
  • 小模型 vs 大模型:企业 AI 落地的成本、性能与场景适配选择
  • 广东省省考备考(第九十天8.30)——判断推理(第十节课)
  • 企业为什么需要部署数据防泄露系统?
  • 第三十一天:数列求和取模
  • C++讲解---如何设计一个类
  • 【lua】模块基础及应用
  • LED灯带离线语音控制方案WTK6900P
  • fork详解(附经典计算题)