找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

181

积分

0

好友

23

主题
发表于 3 天前 | 查看: 4| 回复: 0

在嵌入式项目部署到现场后,最常见的问题包括偶发性死机和运行时间长了后性能下降。后者往往与内存泄漏或内存碎片有关,但现场环境通常只有串口日志和有限的Flash存储,无法使用gdb或Valgrind等工具进行调试。

之前我们分享过开发阶段的内存泄漏检测工具:嵌入式系统内存泄漏检测利器:MTrace。但许多开发者询问是否有适用于运维阶段的检测方案。

图片

本文将分享一个运维态的内存泄漏监测方案:dlmalloc,它能够在不影响系统运行的前提下实现轻量级内存监控。

一、为什么需要在运维阶段进行泄漏检测?

开发阶段的泄漏问题通常可以通过单元测试、静态分析和PC端工具解决。真正棘手的是以下场景:

  • 设备运行数天甚至数周后才逐渐耗尽内存
  • 特定异常网络包或罕见业务流程触发泄漏
  • 线下复现成本高,客户现场环境复杂

这类问题具有共同特征:

  1. 仅在真实负载下出现,实验室环境难以完整模拟
  2. 触发条件组合多样,人工造数据难以捕捉
  3. 设备重启后现象消失,需要重新积累数据

因此,合理方案是在设备上常驻一套轻量级内存监控和泄漏记录机制,能够在问题发生时保留证据。这个检测机制需要满足轻量级、低开销、非侵入、无需调试器或源码修改的要求,且不能影响业务正常运行。

dlmalloc提供的mallinfo/malloc_stats接口结合追踪表,可以搭建这样的基础设施。

二、dlmalloc能提供哪些监控能力?

1. dlmalloc简介

dlmalloc是一个开源轻量级内存分配库,专为嵌入式内存管理设计。它具有内存统计接口、无外部依赖、代码量仅几百行的特点。

官方资源: http://gee.cs.oswego.edu/dl/html/malloc.html https://github.com/ennorehling/dlmalloc

dlmalloc头文件中包含两个对运维非常有用的接口:

  • struct mallinfo mallinfo(void); 返回当前堆的统计信息:总空间、已用空间、空闲块数、可回收空间等
  • void malloc_stats(void); 将详细统计信息打印到stderr(嵌入式系统中通常重定向到串口或日志)

mallinfo结构的关键字段: 图片

  • uordblks:当前已分配字节总数
  • fordblks:当前空闲字节总数
  • ordblks:空闲块数量(结合fordblks可估算碎片情况)
  • arena:从系统获取的总堆空间

通过这些数据,可以在设备上实现内存水位曲线的绘制和监控。

三、基于dlmalloc的运维态泄漏检测方案

运维态泄漏检测可分为两个层次:

  • 微观层:在malloc/free外层包装带来源信息的追踪表,最终列出未释放的分配
  • 宏观层:使用mallinfo监控堆的整体健康状况(是否持续增长、碎片是否增加)

1. 实现malloc/free的追踪表

整体水位监控只能提示存在泄漏,但要定位具体泄漏源需要更细粒度的记录。

方案:使用宏包装所有分配/释放操作,添加来源位置信息并存入小型追踪表

#define USE_DL_PREFIX       // 使用 dlmalloc 前缀
#include "malloc.h"

// 追踪表,用于记录未释放的分配
typedef struct {
    void   *ptr;        // 分配得到的指针地址
    size_t  size;       // 分配的字节数
    const char *file;   // 分配发生的源文件名
    int     line;       // 分配发生的源代码行号
} alloc_record_t;

#define MAX_RECORDS 256
static alloc_record_t g_records[MAX_RECORDS];
static int g_record_count = 0;

void *tracked_malloc(size_t size, const char *file, int line)
{
    void *p = dlmalloc(size);
    if (p && g_record_count < MAX_RECORDS) {
        g_records[g_record_count].ptr  = p;
        g_records[g_record_count].size = size;
        g_records[g_record_count].file = file;
        g_records[g_record_count].line = line;
        g_record_count++;
    }
    return p;
}

void tracked_free(void *ptr)
{
    if (!ptr) return;
    for (int i = 0; i < g_record_count; i++) {
        if (g_records[i].ptr == ptr) {
            g_records[i] = g_records[g_record_count - 1];
            g_record_count--;
            break;
        }
    }
    dlfree(ptr);
}

#define EM_MALLOC(sz)  tracked_malloc((sz), __FILE__, __LINE__)
#define EM_FREE(p)     tracked_free((p))

追踪表示例: 图片

2. 使用mallinfo进行整体水位监控

以下是一个简单的内存看门狗任务实现:

#include "malloc.h"
#include <stdio.h>

typedef struct {
    int peak_uordblks;   // 峰值已分配
    int last_uordblks;   // 上一次记录
} mem_watch_t;

static mem_watch_t g_mem_watch;

void memory_watchdog_task(void)
{
    struct mallinfo info = mallinfo();
    if (info.uordblks > g_mem_watch.peak_uordblks) {
        g_mem_watch.peak_uordblks = info.uordblks;
    }

    // 简单策略:每次打印当前值和相对变化
    int delta = info.uordblks - g_mem_watch.last_uordblks;
    g_mem_watch.last_uordblks = info.uordblks;

    printf("[MEM] used=%dB free=%dB blocks=%d delta=%dB peak=%dB\n",
           info.uordblks, info.fordblks, info.ordblks,
           delta, g_mem_watch.peak_uordblks);
}

将此函数设置为每秒或每分钟执行一次,可以观察到:

  • 特定业务场景结束后,uordblks是否回落到接近初始值
  • 长时间运行后,uordblks是否存在缓慢但持续上升的趋势

四、内存泄漏检测实战演示

1. 完整代码示例

leak_demo.c:

#include <stdio.h>
#include <string.h>

#define USE_DL_PREFIX       // 使用 dlmalloc 前缀
#include "malloc.h"

// 追踪表,用于记录未释放的分配
typedef struct {
    void   *ptr;        // 分配得到的指针地址
    size_t  size;       // 分配的字节数
    const char *file;   // 分配发生的源文件名
    int     line;       // 分配发生的源代码行号
} alloc_record_t;

#define MAX_RECORDS 256
static alloc_record_t g_records[MAX_RECORDS];
static int g_record_count = 0;

void *tracked_malloc(size_t size, const char *file, int line)
{
    void *p = dlmalloc(size);
    if (p && g_record_count < MAX_RECORDS) {
        g_records[g_record_count].ptr  = p;
        g_records[g_record_count].size = size;
        g_records[g_record_count].file = file;
        g_records[g_record_count].line = line;
        g_record_count++;
    }
    return p;
}

void tracked_free(void *ptr)
{
    if (!ptr) return;
    for (int i = 0; i < g_record_count; i++) {
        if (g_records[i].ptr == ptr) {
            g_records[i] = g_records[g_record_count - 1];
            g_record_count--;
            break;
        }
    }
    dlfree(ptr);
}

#define EM_MALLOC(sz)  tracked_malloc((sz), __FILE__, __LINE__)
#define EM_FREE(p)     tracked_free((p))

// 故意制造泄漏
void test1(void)
{
    // 正常释放的缓冲区
    char *buf1 = (char *)EM_MALLOC(64);
    strcpy(buf1, "test1: buf1");
    EM_FREE(buf1);

    // 故意不释放的缓冲区
    char *buf2 = (char *)EM_MALLOC(128);
    strcpy(buf2, "test1: buf2");
}

// 故意制造泄漏
void test2(void)
{
    void *pkt = EM_MALLOC(512);
}

// 报告函数
void report_leaks(void)
{
    printf("\n========== Memory Leak Report ==========\n");
    if (g_record_count == 0) {
        printf("\nNo leaks found\n");
        return;
    }

    // 统计总泄漏字节数
    size_t total = 0;
    for (int i = 0; i < g_record_count; i++) {
        printf(" [%d] %zu bytes @ %p  from %s:%d\n",
               i + 1,
               g_records[i].size,
               g_records[i].ptr,
               g_records[i].file,
               g_records[i].line);
        total += g_records[i].size;
    }
    printf("\nTotal leaked: %zu bytes (%.2f KB)\n", total, total / 1024.0);
}

int main(void)
{
    printf("\n========== Memory Leak Test ==========\n");
    test1();
    test2();

    struct mallinfo info = mallinfo();
    printf("\nCurrently allocated: %d bytes\n", info.uordblks);
    report_leaks();

    return 0;
}

编译运行命令:

gcc -O2 -DUSE_DL_PREFIX leak_demo.c malloc.c -o leak_demo
./leak_demo

运行结果示例: 图片 图片

这已经构成了一个最小可用的泄漏定位工具:

  • 能够报告泄漏大小
  • 能够定位到具体文件和行号
  • 不依赖操作系统或glibc动态库

2. 实际应用中的注意事项

追踪表大小限制

  • MAX_RECORDS需要根据可用RAM调整,如64/128/256
  • 超出限制后可选择:停止记录、覆盖旧记录或仅记录最大块

性能影响控制

  • 建议使用#ifdef MEM_LEAK_TRACE控制开关
  • 仅在疑似问题版本或现场定位阶段启用
  • 或仅对特定模块使用EM_MALLOC/EM_FREE宏替换

图片

通过这套系统监控方案,可以在不影响嵌入式设备正常运行的前提下,有效检测和定位内存泄漏问题,为现场故障排查提供有力工具。

您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区(YunPan.Plus) ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-1 16:23 , Processed in 0.059192 second(s), 38 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 CloudStack.

快速回复 返回顶部 返回列表