前言
目前许多手机、数据卡终端厂商产品普遍还是基于C语言进行开发的,由于C语言动态申请的内存需要程序员负责释放,所以产品中势必会存在内存泄漏。鉴于嵌入式产品内存资源宝贵,积累的泄漏会导致手机无规律死机重启白屏等问题,给产品维护带来了极大的难度。
本文采取的方案使用于目前主流的高通BREW平台(WCDMA、CDMA),大唐联芯平台(TDSCDMA),英飞凌平台(GSM),具有较强的商用价值。
本文以联芯平台为例,其平台的内存申请函数为tp_os_mem_malloc,释放函数为tp_os_mem_free。
内存管理函数的重新定义
目前项目的内存管理采用的C语言Malloc分配内存、Free释放内存的管理方式。针对这一内存管理方式,我们考虑使用一个链表记录每一个Malloc分配的内存块信息,在正常释放后则删除该内存块信息。在监测结束后链表中剩下的内存块便为没有释放的内存块,从而检查该模块的内存泄露情况。内存块的记录结构体主要包括文件名、行号、内存块大小以及Malloc返回的地址。具体采用下面代码进行实现:
typedef struct DTMEMLEAK_BLOCK
{
void *addr;
char filename[100];
int codeline;
unsigned long size; //在手机上使用考虑16位。
struct DTMEMLEAK_BLOCK *next;
}_DTMEMLEAK_BLOCK;
在该结构体中以Malloc返回的地址为标志,用来判别不同的内存块。然后重新定义Malloc函数,在每次调用Malloc分配内存的同时使用上面的结构体记录信息,Malloc函数重新定义代码如下:
void * Memoryleak_Malloc_zx ( unsigned long s, char *filename, int line)
{
_DTMEMLEAK_BLOCK *p_temp = NULL;
p_temp = (_DTMEMLEAK_BLOCK *)tp_os_mem_malloc(sizeof(_DTMEMLEAK_BLOCK));
p_temp->addr = tp_os_mem_malloc(s);
p_temp->codeline = line;
strncpy(p_temp->filename,filename, 100);
p_temp->size = s;
p_temp->next = g_dtmemoryleaktest_head->next;
g_dtmemoryleaktest_head->next = p_temp;
return (p_temp->addr);
}
通过上面的函数便可以在检测模块调用内存时,将调用信息用链表的形式进行存储。其中的g_zxmemoryleaktest_head为全局变量,用来记录链表起始位置的。在调用的内存块使用完毕正常结束后,将内存块进行释放的同时比较待释放内存的地址,如果该地址存在与记录链表中,则从链表删除其信息。Free函数重新定义代码如下:
void Memoryleak_Free_zx (void * p)
{
_DTMEMLEAK_BLOCK *p_temp = g_dtmemoryleaktest_head->next;
_DTMEMLEAK_BLOCK *pre = g_dtmemoryleaktest_head;
while(NULL != p_temp)
{
if (p_temp->addr == p)
{
pre->next = p_temp->next;
tp_os_mem_free( p_temp);
p_temp = NULL;
break;
}
else
{
pre = p_temp;
p_temp = p_temp->next;
}
}
tp_os_mem_free(p);
p = NULL;
return;
}
内存管理接口的定义
在完成重新定义后完成后,需要将检测模块的内存管理和非检测模块的内存管理进行一个逻辑选择。检测模块的内存管理使用我们重新定义的内存管理函数进行检测,而非检测模块的内存管理管理依然使用原来的内存管理函数。这样一方面可以减少检测机制对原有程序效率的影响,另一方面可以缩小检测范围提高检测信息的有用率。具体实现代码如下:
void * Memoryleak_Malloc (unsigned long s, char *filename, int line)
{
void * p_temp = NULL;
if (!g_dtmemoryleak_isbegin)
{
p_temp = tp_os_mem_malloc(s);
}
else
{
//return Memoryleak_Malloc_zx (s, filename, line);
p_temp = Memoryleak_Malloc_zx (s, filename, line);
}
if (p_temp != NULL)
{
memset(p_temp, 0, s ); /*将内存初始化*/
}
return (p_temp);
}
void Memoryleak_Free (void * p)
{
if (p != NULL)
{
if (!g_dtmemoryleak_isbegin) //control
{
tp_os_mem_free(p);
p = NULL;
return;
}
else
{
Memoryleak_Free_zx(p);
return;
}
}
return; }
在模块中所有内存管理都统一调用上面两个函数,由该函数根据情况进行逻辑分配。代码中g_zxmemoryleak_isbegin为一个全局变量,在进入检测模块前赋值为TURE,这样保证检测区域均采用的重定义的内存管理函数;在离开检测区域是赋值为FALSE,保证了其他模块的运行效率。
在Begin函数中主要是对g_zxmemoryleak_isbegin赋值为TRUE,然后对记录链表进行初始化。而End函数主要是输出最后的文件名、行号等检测信息,并将释放记录链表所占用的内存空间同时对g_zxmemoryleak_isbegin赋值为FALSE。在上面代码中注意第119和144行代码,该行代码的作用主要是为了避免重复的Begin和End。主要因为模块的入口和出口往往不只一个,我们需要在多处添加Begin和End,这样可能会重复连续两次Begin造成死机。
检测范围的设定
最后是对检测模块的设置,在代码中我们采用两个函数实现。在进入模块前添加Begin函数,在结束模块时添加End函数。这样,整个模块便设定为检测模块。函数实现代码如下:
void Test_Memoryleak_Begin()
{
Trace(0,0,"Test_Memoryleak_Begin begin !!!!!!!!!!!");
if(NULL != g_dtmemoryleaktest_head)
{
return;
}
g_dtmemoryleaktest_head = (_DTMEMLEAK_BLOCK *)tp_os_mem_malloc(sizeof( _DTMEMLEAK_BLOCK));
g_dtmemoryleaktest_head->next = NULL;
g_dtmemoryleaktest_head->addr = NULL;
g_dtmemoryleaktest_head->codeline = 0;
memset(g_dtmemoryleaktest_head->filename,0,100*sizeof(char));
g_dtmemoryleaktest_head->size = 0;
g_dtmemoryleak_isbegin = TRUE;
return ;
}
void Test_Memoryleak_End()
{
_DTMEMLEAK_BLOCK *p_temp = g_dtmemoryleaktest_head->next;
_DTMEMLEAK_BLOCK *p_pre = NULL;
int totalsize=0;
Trace(0,0, "memoryleaktest_result: /n");
Trace(0,0, "g_dtmemoryleaktest_head = %d",g_dtmemoryleaktest_head);
if(NULL == g_dtmemoryleaktest_head)
{
return;
}
while(NULL != p_temp )
{
Trace(0,0,"filename = %s ", p_temp->filename );
Trace(0,0,"codeline = %d ", p_temp->codeline );
Trace(0,0,"size = %d /n", p_temp->size );
p_pre = p_temp;
totalsize += p_temp->size;
p_temp = p_temp->next;
tp_os_mem_free(p_pre);
}
Trace(0,0,"totalsize = %d /n", totalsize );
tp_os_mem_free(g_dtmemoryleaktest_head);
g_dtmemoryleaktest_head = NULL;
g_dtmemoryleak_isbegin = FALSE;
return ;
}
实际运行情况
查找到原有的内存管理是在Sc_share_mem.h文件中进行宏定义转换的,所以更改原有的内存管理接口为新建的内存管理接口。并设置宏开关ZXMEMORYLEAKTEST进行控制管理。
其中接着在原工程中添加内存泄漏检测代码文件,并在项目控制文件APP_Config.h中打开宏开关,最后在涉及到的文件中添加上面函数的声明和全局变量的外部引用。在Email模块的进入和退出部分添加相应的Begin和End函数后,最后工程的输出结果如下: