设为首页 加入收藏

TOP

如何在linux下检测内存泄漏(四)
2011-03-18 13:13:13 来源:IBM 作者: 【 】 浏览:2907
Tags:何在 linux 检测 内存 泄漏
行过程中,一直没有任何线程进行内存释放。当程序退出的时候,该数据存储中的指针值所指向的内存块被依次释放。

2. 程序的N个线程进行内存分配,并将指针传递给一个数据存储,由M个线程从数据存储进行数据处理和内存释放。由于 N 远大于M,或者M个线程数据处理的时间过长,导致内存分配的速度远大于内存被释放的速度。但是在程序退出的时候,数据存储中的指针值所指向的内存块被依次释放。

之所以说他危害性更大,是因为很不容易这种问题找出来,程序可能连续运行几个十几个小时没有问题,从而通过了不严密的系统测试。但是如果在实际环境中 7×24 小时运行,系统将不定时的崩溃,而且崩溃的原因从 log 和程序表象上都查不出原因。

为了将这种问题也挑落马下,我们增加了一个动态检测模块 MemSnapShot,用于在程序运行过程中,每隔一定的时间间隔就对程序当前的内存总使用情况和内存分配情况进行统计,以使用户能够对程序的动态内存分配状况进行监视。

当客户使用 MemSnapShot 进程监视一个运行中的进程时,被监视进程的内存子系统将把内存分配和释放的信息实时传送给MemSnapShot。MemSnapShot 则每隔一定的时间间隔就对所接收到的信息进行统计,计算该进程总的内存使用量,同时以调用new进行内存分配的文件名和行号为索引值,计算每个内存分配动作所分配而未释放的内存总量。这样一来,如果在连续多个时间间隔的统计结果中,如果某文件的某行所分配的内存总量不断增长而始终没有到达一个平衡点甚至回落,那它一定是我们上面所说到的两种问题之一。

在实现上,内存检测子系统的全局对象(appMemory)的构造函数中以自己的当前 PID 为基础 key 值创建一个消息队列,并在operator new 和 operator delete 被调用的时候将相应的信息写入消息队列。MemSnapShot 进程启动时需要输入被检测进程的 PID,而后通过该 PID 组装 key 值并找到被检测进程创建的消息队列,并开始读入消息队列中的数据进行分析统计。当得到operator new 的信息时,记录内存分配信息,当收到 operator delete 消息时,删除相应的内存分配信息。同时启动一个分析线程,每隔一定的时间间隔就计算一下当前的以分配而尚未释放的内存信息,并以内存的分配位置为关键字进行统计,查看在同一位置(相同文件名和行号)所分配的内存总量和其占进程总内存量的百分比。

图4 是一个正在运行的 MemSnapShot 程序,它所监视的进程的动态内存分配情况如图所示:

 
图四

在支持 MemSnapShot 过程中的实现上的唯一技巧是--对于被检测进程异常退出状况的处理。因为被检测进程中的内存检测子系统创建了用于进程间传输数据的消息队列,它是一个核心资源,其生命周期与内核相同,一旦创建,除非显式的进行删除或系统重启,否则将不被释放。

不错,我们可以在内存检测子系统中的全局对象(appMemory)的析构函数中完成对消息队列的删除,但是如果被检测进程非正常退出(CTRL+C,段错误崩溃等),消息队列可就没人管了。那么我们可以不可以在全局对象(appMemory)的构造函数中使用 signal 系统调用注册 SIGINT,SIGSEGV 等系统信号处理函数,并在处理函数中删除消息队列呢?还是不行,因为被检测进程完全有可能注册自己的对应的信号处理函数,这样就会替换我们的信号处理函数。最终我们采取的方法是利用 fork 产生一个孤儿进程,并利用这个进程监视被检测进程的生存状况,如果被检测进程已经退出(无论正常退出还是异常退出),则试图删除被检测进程所创建的消息队列。下面简述其实现原理:

在全局对象(appMemory)构造函数中,创建消息队列成功以后,我们调用 fork 创建一个子进程,而后该子进程再次调用 fork 创建孙子进程,并退出,从而使孙子进程变为一个"孤儿"进程(之所以使用孤儿进程是因为我们需要切断被检测进程与我们创建的进程之间的信号联系)。孙子进程利用父进程(被检测进程)的全局对象(appMemory)得到其 PID 和刚刚创建的消息队列的标识,并传递给调用 exec 函数产生的一个新的程序映象--MemCleaner。

MemCleaner 程序仅仅调用 kill(pid, 0);函数来查看被检测进程的生存状态,如果被检测进程不存在了(正常或者异常退出),则 kill 函数返回非 0 值,此时我们就动手清除可能存在的消息队列。

 


6.实现上的问题:嵌套delete

在"错误方式删除带来的问题"一节中,我们对 delete operator 动了个小手术--增加了两个全局变量(DELETE_FILE,DELETE_LINE)用于记录本次 delete 操作所在的文件名和行号,并且为了同步对全局变量(DELETE_FILE,DELETE_LINE)的访问,增加了一个全局的互斥锁。在一开始,我们使用的是 pthread_mutex_t,但是在测试中,我们发现 pthread_mutex_t 在本应用环境中的局限性。

例如如下代码:

class B {…}; class A { public: A() {m_pB = NULL}; A(B* pb) {m_pB = pb;}; ~A() { if (m_pB != NULL) 行号1 delete m_pB; //这句最要命 }; private: class B* m_pB; …… } int main() { A* pA = new A(new B); …… 行号2 delete pA; }

在上述代码中,main 函数中的一句 delete pA 我们称之为"嵌套删除",即我们 delete A 对象的时候,在A对象的析构执行了另一个 delete B 的动作。当用户使用我们的内存检测子系统时,delete pA 的动作应转化为以下动作:

上全局锁 全局变量(DELETE_FILE,DELETE_LINE)赋值为文件名和行号2 delete operator A 调用~A() 上全局锁 全局变量(DELETE_FILE,DELETE_LINE)赋值为文件名和行号1 delete operator B 调用~B() 返回~B() 调用operator delete B 记录全局变量(DELETE_FILE,DELETE_LINE)值1并清除全局变量(DELETE_FILE,DELETE_LINE)值 打开全局锁 返回operator delete B 返回delete operator B 返回~A() 调用 operator delete A 记录全局变量(DELETE_FILE,DELETE_LINE)值1并清除全局变量(DELETE_FILE,DELETE_LINE)值 打开全局锁 返回operator d
首页 上一页 1 2 3 4 5 下一页 尾页 4/5/5
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇C++断想 下一篇C 编程最佳实践