一个简单的可移植 模块/系统动态内存 跟踪方法
在嵌入式系统或一些内存申请,释放频繁的场合,很难统计当前使用的动态内存,和整个软件模块运行过程中的内存使用的峰值;或者模块卸载时内存有无完全释放,还有就是动态内存的踩踏检查。针对这种需求,本文给出一种 简单的解决办法,并用 c code 描述出来。
感性认识
本文命名所建立的跟踪模块为mtrace
mtrace可以用来 跟踪当前 系统/模块的内存 使用情况 ,以及估算系统/模块的占用内存的极值 , 系统/模块 卸载时的 内存 泄露情况。
模块内存跟踪只在 dbg模式下有效 ,rom 下自动 变成mode 0
模块内存跟踪 有 0,1,2,3 四种模式 ,其中 mode 0,3 没有内存使用统计;mode 1,2有内存使用统计,具体说明如下。
Mode 总开关 由 FS_TRACE_MEM 控制
FS_TRACE_MEM =0 ,(mode 0) 其上所有 系统/模块 不使用内存跟踪
FS_TRACE_MEM =1 ,(mode 1)其上所有 系统/模块使用内存跟踪,只统计 系统/模块 占用总量大小,具体在各系统/模块 声明的TRACE_MEMSTAT 中,如fat 为fat_memstat,ntfs 为ntfs_memstat,hfs+为hfsplus_memstat ;default_memstat 为其上所有 系统/模块 内存跟踪统计,如下图
说明:
int calloc; 当前模块调用 calloc申请 的字节数
int malloc; 当前模块调用 malloc申请 的字节数
int realloc; 当前模块调用 realloc申请 的字节数
int total; 当前模块迄今 申请内存 的总字节数 (以上3项总和)
int max_once; 模块迄今 一次申请 最多字节数
int max_total; 模块迄今 使用最多情况下的 总字节数,即峰值
int calloc_cnt; 模块 调用 有效calloc的次数
int malloc_cnt; 模块 调用 有效malloc的次数
int realloc_cnt; 模块 调用 有效realloc的次数
int total_cnt; 模块 申请内存 的总次数 (以上3项总和)
int max_total_cnt; 模块迄今 申请内存 的总次数最大值
FS_TRACE_MEM =2 ,(mode 2)其上所有 系统/模块 使用内存跟踪,包括统计占用总量大小,内存分配日志 ,具体在各 系统/模块 声明的TRACE_MEMSTAT 中 ;而且日志 极限数目 由每个系统/模块 的MAX_ALLOC_ENTRYS 宏 控制 。如fat为fat_memstat,ntfs 为ntfs_memstat,hfs+为hfsplus_memstat ;default_memstat 为其上所有 系统/模块 内存跟踪统计,跟踪日志在memstat结构的 log结构中,如下图
说明:
int calloc;
int malloc;
int total;
int realloc;
int max_once;
int max_total;
int calloc_cnt;
int malloc_cnt;
int realloc_cnt;
int total_cnt;
int max_total_cnt; 同上
int flag; //日志系内部标志
u16 limit; //系统/模块最多可容纳 日志数 ,与MAX_ALLOC_ENTRYS有关
s16 max_index; //最后一个 占用日志 索引
u16 free_index; //第一个 空闲日志 索引
u16 alloc_count; //迄今 已使用 日志数
u16 max_alloc_count; //迄今 使用最多日志情况下的 日志数
char *logstr[MAX_ALLOC_ENTRYS]; //日志数组
如果内存申请成功 ,写入有效日志, 第三个逗号后为 申请内存处 所在文件的 文件名字符串; 第二个逗号后为 申请内存处 该文件的所在行数; 第一个逗号后为在该处 申请的总有效字节数;第一个逗号内为在该处 申请的总有效次数,
如果 该文件 对应 line 的申请 内存被全部释放 , 该处日志 = 0,如上图13,15,16
如果 系统/模块退出, 所有内存释放 ,如下图,可以用来跟踪 系统/模块 内存的泄漏情况
从上图可以看看出 ,系统在任何调试时刻 ,可以看到模块占用的内存统计和申请情况。大大方便 用户对 模块使用内存的评估
如何使用:
举例如下:假设要评估的模块是 hfs
(1)在 评估 模块或系统的 *.h或*.c 文件内, 加入
#define TRACE_MEMSTAT & hfs_memstat //指定跟踪模块命名
#define MAX_ALLOC_ENTRYS 128 //仅FS_TRACE_MEM =2有用
#include "mtarce.h"
DECLARE_MEMSTAT(hfs_memstat);
#define hfs_calloc( size) mtrace_calloc(size)
#define hfs_malloc( size) mtrace_malloc(size )
#define hfs_realloc(p, size) mtrace_realloc( p ,size )
#define hfs_free(p) mtrace_free(p)
确保每个c文件 都会编译到
(2)某一c文件 加入
DEFINE_MEMSTAT ((hfs_memstat); //仅需一次
(3)系统项目编译链接 加入 mtrace.c 文件
(4)以后在模块(或系统)凡是使用 内存分配例程的地方 都替换为 mtrace_XXX或 hfs_XXX对应例程。
通过以上几步,即可应用mtrace 的动态 内存使用情况 跟踪
源码说明:
mtrace 由2个可移植文件组成mtrace.h, mtrace.c
其中mtrace.h 为算法配置和声明,mtrace.c为算法实现,详细列举如下
mtrace.h
#ifndef MTRACE_H
#define MTRACE_H
configurations
#include "includes.h"
#ifndef __ROM_
#define FS_PRINT_ENABLE 1
#define FS_TRACE_MEM 2 //0,1,2,3 可以修改
#else //dont modify below
#define FS_PRINT_ENABLE 0
#define FS_TRACE_MEM 0
#endif
//fit to yourown requirement
#define u16 unsigned short
#define s16 signed short
//#define size_t int
#define raw_calloc( n , size) calloc(n ,size)
#define raw_malloc( size) malloc(size)
#define raw_realloc(p, size) realloc( p ,size )
#define raw_free(p) free( p )
#define DECLEAR_KEY(x) OS_EVENT *x=NULL
#define INIT_THREAD_MUTEX() AVSemCreate(1)
#define THREAD_LOCK(x) {INT8U err; AVSemPend(x,0,&err); }
#define THREAD_UNLOCK(x) AVSemPost(x)
#ifndef ENOMEM
#define ENOMEM 12
#endif
#if( FS_TRACE_MEM>2)
#define mtrace_calloc(n, size) raw_calloc(n,size)
#define mtrace_malloc( size) raw_malloc(size)
#define mtrace_realloc(p, size) raw_realloc( p ,size )
#define mtrace_free(p) raw_free( p )
#define DEFINE_MEMSTAT(stat) //
#define DECLARE_MEMSTAT(stat) //
#elif( FS_TRACE_MEM>0)
#ifndef MAX_ALLOC_ENTRYS
#define MAX_ALLOC_ENTRYS 128
#endif
#define memstat_log_inited 0x01
#define memstat_log_allused 0x02
#define memstat_log_index_invalid -1
typedef struct
{
int calloc;
int malloc;
int realloc;
int total;
int max_once;
int max_total;
int calloc_cnt;
int malloc_cnt;
int realloc_cnt;
int total_cnt;
int max_total_cnt;
struct
{
int flag;
u16 limit;
#if( FS_TRACE_MEM&2)
s16 max_index; //max valid index,fast find matched log
u16 free_index; //first free log index,fast for next log if all exist not matched
u16 alloc_count; //current total used log entrys
u16 max_alloc_count;
char *logstr[MAX_ALLOC_ENTRYS];
// char **logstr;
#endif
}log;
}memstat;
#define DEFINE_MEMSTAT(hinst) memstat hinst={0,0,0,0,0, 0,0,0,0,0,0,{0,MAX_ALLOC_ENTRYS}}
#define DECLARE_MEMSTAT(hinst) extern memstat hinst
void *mtrace_calloc_trace(size_t size,int line, char *file,memstat *stat);
void *mtrace_malloc_trace(size_t size,int line, char *file,memstat *stat);
void *mtrace_realloc_trace(void *old,size_t size,int line, char *file,memstat *stat);
void mtrace_free_trace (void *pmen,memstat *stat);
DECLARE_MEMSTAT(default_memstat);
#ifndef TRACE_MEMSTAT
#define TRACE_MEMSTAT &default_memstat
#endif
#define mtrace_calloc( size) mtrace_calloc_trace(size, __LINE__, __FILE__,TRACE_MEMSTAT)
#define mtrace_malloc( size) mtrace_malloc_trace(size ,__LINE__, __FILE__,TRACE_MEMSTAT)
#define mtrace_realloc(p, size) mtrace_realloc_trace( p ,size ,__LINE__, __FILE__,TRACE_MEMSTAT)
#define mtrace_free(p) mtrace_free_trace( p , TRACE_MEMSTAT)
#else
#define DEFINE_MEMSTAT(stat) //
#define DECLARE_MEMSTAT(stat) //
void *mtrace_calloc(size_t size);
void *mtrace_malloc(size_t size);
void *mtrace_realloc(void *old,size_t size);
void mtrace_free(void pmen);
#endif
#if FS_PRINT_ENABLE
void mtrace_log_redirect(const char *function, const char *file, int line,
const char *FORMAT, ...);
#define mtrace_log_error(arg...) mtrace_log_redirect(__FUNCTION__,__FILE__,__LINE__,arg)
#else
#define mtrace_log_error(arg...) //
#endif
#endif
mtarce.c
#include " mtarce.h"
#if( FS_TRACE_MEM>2)
#elif( FS_TRACE_MEM>0)
#define MH_MAGIC_MASK 0xFFFFFFFC
#define MH_TYPE_MASK 0x00000003
#define MH_MAGIC_ALLOC 0xCCCCCCCC
#define MH_MAGIC_FREE 0xEEEEEEEE
#define MH_TYPE_C 0
#define MH_TYPE_M 1
#define MH_TYPE_R 2
typedef struct
{
int magic; //0xcccc , low nibble for type
int size;
#if( FS_TRACE_MEM&2)
int logindex;
#endif
}meminfo;
#define meminfo_len sizeof(meminfo)
DEFINE_MEMSTAT(default_memstat); //total mem trace for all fs
#if( FS_TRACE_MEM&2)
#define MAX_ALLOC_FILNAMELEN 32
#define MEMLOG_STATEFREE 0
#define thisfs_memlog stat->log
static void init_memlog(memstat *stat)
{
if(thisfs_memlog.flag&memstat_log_inited)
return ;
#if 0
char *log =(char *) calloc(sizeof(char *),thisfs_memlog.limit);
if (!log)
{
fs_log_error("Failed to calloc %d logs/n", (long)thisfs_memlog.limit);
return ;
}
thisfs_memlog.logstr = (char **)log;
#endif
for (int i=0;i<thisfs_memlog.limit;i++)
{
thisfs_memlog.logstr[i]=MEMLOG_STATEFREE;
}
thisfs_memlog.max_index=-1;
thisfs_memlog.free_index=0;
thisfs_memlog.alloc_count=0;
thisfs_memlog.flag |= memstat_log_inited;
if(default_memstat.log.flag&memstat_log_inited)
return ;
// default_memstat.log.limit = MAX_ALLOC_ENTRYS;
#if 0
log =(char *) calloc(sizeof(char *),default_memstat.log.limit);
if (!log)
{
fs_log_error("Failed to calloc %d logs/n", (long)default_memstat.log.limit);
return ;
}
thisfs_memlog.logstr = (char **)log;
#endif
for ( i=0;i < default_memstat.log.limit;i++)
{
default_memstat.log.logstr[i]=MEMLOG_STATEFREE;
}
default_memstat.log.max_index=-1;
default_memstat.log.free_index=0;
default_memstat.log.alloc_count=0;
default_memstat.log.flag |= memstat_log_inited;
}
#define logfmt "%d,%d,%d,%s"
static size_t o_size;
static char o_file[MAX_ALLOC_FILNAMELEN];
static int o_line;
static int o_times;
static int register_log(meminfo *p,int line, char *file,memstat *stat)
{
int i,j;
int retry=0;
size_t size = p ->size;
init_memlog(stat);
p->logindex = memstat_log_index_invalid;
//use the already log entry
for( i=0 ;i<=thisfs_memlog.max_index;i++)
{
if(thisfs_memlog.logstr[i]==MEMLOG_STATEFREE)
continue;
sscanf(thisfs_memlog.logstr[i], logfmt, &o_times,&o_size,&o_line, o_file);
if(/*(o_size==size)&&*/(o_line==line)&&(strcmp(o_file,file)==0)&&o_times>0)
{
snprintf((char*) thisfs_memlog.logstr[i] ,MAX_ALLOC_FILNAMELEN, logfmt ,o_times+1,size+o_size,line,file);
p->logindex = i;
return 0;
}
}
//create new log
if(thisfs_memlog.flag & memstat_log_allused)
{
fs_log_info("all used out now/n");
return -1;
}
j=thisfs_memlog.free_index;
if(thisfs_memlog.logstr[j])
{
fs_log_error("already used now/n");
for(i = 0 ;i<thisfs_memlog.limit;i++)
{
if(thisfs_memlog.logstr[i]==MEMLOG_STATEFREE)
break;
}
if(i==thisfs_memlog.limit)
{
thisfs_memlog.flag|=memstat_log_allused;
fs_log_error("all used out now/n");
return -1;
}
j = i;
}
thisfs_memlog.logstr[j] =(char *) malloc(MAX_ALLOC_FILNAMELEN);
if (!thisfs_memlog.logstr[j])
{
fs_log_error("Failed to malloc %d bytes/n", (long)size);
thisfs_memlog.free_index = i;
return -1;
}
snprintf((char*) thisfs_memlog.logstr[j] ,MAX_ALLOC_FILNAMELEN, logfmt, 1,size,line,file);
thisfs_memlog.alloc_count++;
if(thisfs_memlog.max_alloc_count<thisfs_memlog.alloc_count)
thisfs_memlog.max_alloc_count=thisfs_memlog.alloc_count;
if(thisfs_memlog.max_index< j)
thisfs_memlog.max_index=j;
i = j+1 ;
again:
if(thisfs_memlog.alloc_count<thisfs_memlog.limit) //find free log entry
{
for( ;i<thisfs_memlog.limit;i++)
{
if(thisfs_memlog.logstr[i]==MEMLOG_STATEFREE)
break;
}
if(i==thisfs_memlog.limit )
{
if(!retry)
{
i = 0 ;
retry = 1;
goto again;
}
thisfs_memlog.flag|=memstat_log_allused;
}
else
{
thisfs_memlog.free_index = i;
}
}
else
{
thisfs_memlog.flag |= memstat_log_allused;
}
p->logindex = j;
return 0;
}
static void unregister_log(meminfo *p,memstat *stat)
{
int j = p->logindex;
if(j<0||j>thisfs_memlog.limit)
return ;
if(thisfs_memlog.logstr[j]==MEMLOG_STATEFREE)
{
fs_log_error("already free /n");
return ;
}
sscanf(thisfs_memlog.logstr[j], logfmt, &o_times,&o_size,&o_line, o_file);
if(o_times>1)
{
snprintf((char*) thisfs_memlog.logstr[j] ,MAX_ALLOC_FILNAMELEN, logfmt ,--o_times,o_size-p->size,o_line,o_file);
return ;
}
free(thisfs_memlog.logstr[j]);
thisfs_memlog.logstr[j]=MEMLOG_STATEFREE;
if((thisfs_memlog.flag & memstat_log_allused) || (thisfs_memlog.free_index> j))
thisfs_memlog.free_index =j;
thisfs_memlog.alloc_count--;
if(thisfs_memlog.max_index== j)
{
for( ;j>=0&&thisfs_memlog.logstr[j]==MEMLOG_STATEFREE;j--)
thisfs_memlog.max_index--;
}
thisfs_memlog.flag &=~memstat_log_allused;
}
#endif
static OS_EVENT *memstat_sem=NULL; // perhaps this module will run on multiple task
static int updata_meminfo(meminfo *p,int type,int size ,memstat *stat ,int line, char *file)
{
int ret =0;
if(!memstat_sem)
{
memstat_sem = AVSemCreate (1);
if(!memstat_sem)
{
fs_log_error("memstat_sem create fail/n", p);
return -1;
}
}
AVSemPend(memstat_sem,0,(INT8U *) &ret);
if(size>0)
{
p->magic=MH_MAGIC_ALLOC|type;
p->size=size;
if(type==MH_TYPE_C)
{
stat->calloc+=size;
stat->calloc_cnt++;
}
else if(type==MH_TYPE_M)
{
stat->malloc +=size ;
stat->malloc_cnt++;
}
else
{
stat->realloc+=size;
stat->realloc_cnt++;
}
stat->total+=size;
stat->total_cnt++;
if(stat->max_once<size)
stat->max_once=size;
if(stat->max_total<stat->total)
stat->max_total=stat->total;
if(stat->max_total_cnt<stat->total_cnt)
stat->max_total_cnt=stat->total_cnt;
if(stat!= &default_memstat)
{
if(type==MH_TYPE_C)
{
default_memstat.calloc+=size;
default_memstat.calloc_cnt++;
}
else if(type==MH_TYPE_M)
{
default_memstat.malloc +=size ;
default_memstat.malloc_cnt++;
}
else
{
default_memstat.realloc+=size;
default_memstat.realloc_cnt++;
}
default_memstat.total+=size;
default_memstat.total_cnt++;
if(default_memstat.max_once<size)
default_memstat.max_once=size;
if(default_memstat.max_total<default_memstat.total)
default_memstat.max_total=default_memstat.total;
if(default_memstat.max_total_cnt<default_memstat.total_cnt)
default_memstat.max_total_cnt=default_memstat.total_cnt;
}
#if( FS_TRACE_MEM&2)
ret = register_log(p,line,file,stat);
#endif
}
else
{
type = p->magic&MH_TYPE_MASK;
if(p->magic==MH_MAGIC_FREE)
{
fs_log_error("free 0x%x again/n", p);
AVSemPost(memstat_sem);
return -1;
}
if((p->magic&MH_MAGIC_MASK)!=MH_MAGIC_ALLOC||type>MH_TYPE_R||type<MH_TYPE_C)
{
fs_log_error("free 0x%x mismatch/n", p);
AVSemPost(memstat_sem);
return -1;
}
p->magic=MH_MAGIC_FREE; //avoid free again
size=p->size;
if(type==MH_TYPE_C)
{
stat->calloc -=size ;
stat->calloc_cnt--;
}
else if(type==MH_TYPE_M)
{
stat->malloc -=size ;
stat->malloc_cnt--;
}
else
{
stat->realloc -=size ;
stat->realloc_cnt--;
}
stat->total-=size ;
stat->total_cnt--;
if(stat!= &default_memstat)
{
if(type==MH_TYPE_C)
{
default_memstat.calloc -=size ;
default_memstat.calloc_cnt--;
}
else if(type==MH_TYPE_M)
{
default_memstat.malloc -=size ;
default_memstat.malloc_cnt--;
}
else
{
default_memstat.realloc -=size ;
default_memstat.realloc_cnt--;
}
default_memstat.total-=size ;
default_memstat.total_cnt--;
}
#if( FS_TRACE_MEM&2)
unregister_log(p,stat);
#endif
}
AVSemPost(memstat_sem);
return ret;
}
/**
* ntmtrace_calloc
*
* Return a pointer to the allocated memory or NULL if the request fails.
*/
void *mtrace_calloc_trace(size_t size,int line, char *file,memstat *stat)
{
meminfo *p;
if(size<=0)
return NULL;
p =(meminfo *) calloc(1,size+meminfo_len);
if (!p)
{
fs_log_error("Failed to calloc %d bytes/n", (long)size);
errno = ENOMEM;
return NULL;
}
updata_meminfo(p, MH_TYPE_C,size,stat,line,file);
return (char *)p+meminfo_len;
}
void *mtrace_malloc_trace(size_t size,int line, char *file,memstat *stat)
{
meminfo *p;
if(size<=0)
return NULL;
p =(meminfo *) malloc(size+meminfo_len);
if (!p)
{
fs_log_error("Failed to malloc %d bytes/n", (long)size);
errno = ENOMEM;
return NULL;
}
updata_meminfo(p, MH_TYPE_M,size,stat,line,file);
return (char *)p+meminfo_len;
}
void *mtrace_realloc_trace(void *old,size_t size,int line, char *file,memstat *stat)
{
meminfo *p;
int type ;
if(size<=0)
return NULL;
if(old)
{
p =(meminfo *) ((char *)old -meminfo_len) ;
if(updata_meminfo(p, -1,-1,stat , -1 ,NULL)!=0)
{
return NULL;
}
type = MH_TYPE_R ;
}
else
{
p = NULL;
type = MH_TYPE_M ;
}
p =(meminfo *) realloc(p,size+meminfo_len);
if (!p)
{
fs_log_error("Failed to realloc %d bytes/n", (long)size);
errno = ENOMEM;
return NULL;
}
updata_meminfo(p, type,size,stat,line,file);
return (char *)p+meminfo_len;
}
void mtrace_free_trace(void *pmen,memstat *stat)
{
if(pmen)
{
meminfo *p =(meminfo *) ((char *)pmen -meminfo_len) ;
if(updata_meminfo(p, -1,-1,stat, -1,NULL)==0)
{
free(p);
}
}
}
#else
void *mtrace_calloc(size_t size)
{
void *p;
if(size<=0)
return NULL;
p = calloc(1,size);
if (!p)
{
fs_log_error("Failed to calloc %d bytes/n", (long)size);
errno = ENOMEM;
}
return p;
}
void *mtrace_malloc(size_t size)
{
void *p;
if(size<=0)
return NULL;
p = malloc(size);
if (!p)
{
fs_log_error("Failed to malloc %d bytes/n", (long)size);
errno = ENOMEM;
}
return p;
}
void *mtrace_realloc(void *old,size_t size)
{
void *p;
if(size<=0)
return NULL;
p = realloc(old,size);
if (!p)
{
fs_log_error("Failed to realloc %d bytes/n", (long)size);
errno = ENOMEM;
}
return p;
}
void mtrace_free(void *pmen)
{
if(pmen)
{
free(pmen);
}
}
#endif
#if FS_PRINT_ENABLE
extern INT32U OSTime ;
void mtrace_log_redirect(const char *function, const char *file, int line,
const char *FORMAT, ...)
{
OS_CPU_SR cpu_sr;
// INT32U tick = AVTimeGet(); //avoid OS_EXIT_CRITICAL in printf
va_list args;
OS_ENTER_CRITICAL();
printf("/n%s (%s,line=%d,tick=%d)/n--" ,function,file,line,OSTime);
va_start (args, FORMAT);
vprintf (FORMAT, args);
va_end (args);
OS_EXIT_CRITICAL();
}
#endif