如何自动检查内存泄漏和句柄耗尽

如何自动检查内存泄漏和句柄耗尽

1. 背景

当程序的子模块数量和规模扩大以后,在开发阶段,系统长时间容许后常常会碰到下面一些bug:程序员

  • 内存泄漏。随着时间容许,系统可用的内存愈来愈少,最后kernel 出现oom 错误;
  • 文件句柄耗尽。程序能够打开的文件、套接字、管道愈来愈少,最后出错在用完了最后一个可用句柄的代码附近;
  • 死锁。线程拥有一把锁A,正在申请锁B;但在此时锁B被另一个线程拥有,且那个线程又在申请锁A。造成一个循环等待、占用且不可释放的状态。

调试这些问题,固然能够从代码流程和逻辑出发,结合ps/gdb/proc/core等命令和信息,一步步挖出root cause。但通常要求对代码、线程关系和相关命令比较熟悉,通常耗时较长。因此,通常大型公司都封装了标准的glibc,作了一个wraaper,而后再wrapper里面加入了对上面调试的支持。还有的可采用专业内存泄漏等检查工具,去作代码检测。那么,对于咱工程师而言,可否能本身设计并实现一个资源检查工具呢?数据结构

2. 原理

针对上面的三个例子使用中的资源,咱们能够概括成两类:数量有限的共享资源,好比上面空间有限的内存和数量有限的文件句柄;须要独占的互斥资源,好比上面例子中提到的锁。 下面就分别针对这两种状况,分别展开分析。app

2.1 对共享资管的检查

共享资源的特色是:总量有限,经过申请接口得到,使用完了以后经过释放接口归还。为了保证不浪费资源,这就要求程序在使用完了申请获得的资源以后,必须及时释放。而共享资源出现问题的状况,大部分是因为程序员遗忘没有释放形成的。所以,须要一种内部机制可以记录哪些资源使用了尚未释放,能够经过下面的步骤实现:ide

  • 预备一张表,初始化为空;
  • 分配的时候,把刚分配的资源的地址等信息记录到表中去;
  • 释放的时候,把将释放的资源的地址对应的信息从表中移除;
  • 检查的时候:表为空就代表申请的资源都已释放,不然还有资源没被释放;

2.2 对独享资管的检查

独享资源的特色是:互斥使用,基本上是先到先得,经过标志设置是否以备占有。为了保证不死锁,这就要求:程序在申请某个互斥资源的时候,须要检查它已经拥有的资源,是否被它正在申请的互斥资源的拥有者申请。
若是是,会死锁;不然,不会死锁。一样也能够经过下面的步骤实现:函数

  • 预备一张映射表,初始化为空:它描述一个用户拥有哪些互斥资源,同时根据互斥资源可以索引到它的拥有者;
  • 分配检查的时候:检查当前用户已经拥有的资源,是否被它正在申请的互斥资源的拥有者申请。同时让这个互斥资源可以索引到当前正在申请它的用户;
  • 执行分配的时候:把刚分配的互斥资源的地址添加到当前用户拥有的互斥资源的列表中去,同时让这个互斥资源可以索引到当前拥有它的用户。
  • 释放的时候,把将释放的资源的地址从互斥资源列表移除,断开这个互斥资源和当前用户的索引关系。
  • 多个用户死锁时的分析:

3. 实现

根据上面原理的分析,咱们不难结合以前讲过的xlink、程序堆栈等技巧,选用合适的数据结构来实现。工具

3.1 共享资源检查的实现

根据2.1中的分析,须要先构造一张表来记录这些资源的地址,这张表要求插入方便,删去也迅速。为此,咱们能够用基于平衡二叉树、优先级队列或者hash的方法去实现这个表。对这个表的操做包括PQInsert()/PQRemove()/PQEmpty()等。 此后,就能够开始参考下面列出的针对共享资源泄漏检查的步骤去实现了。线程

3.1.1 声明支持资源泄漏检查的wrapper函数

能够基于标准的open/close/malloc/free等直接申请、是否公共资源的函数,去实现wrapper。
下面以open()、close()为例,伪码示例以下:
int wrapper_open(char * dev);
int wrapper_free(int fd);设计

3.1.2 实现支持资源泄漏检查的wrapper函数

仍是下面以open()、close()为例:调试

#define FILE ('f'<<24|'i<<16|'l'<<8|'e')

int wrapper_open(char * dev)
{
    int fd = real_open(dev);
    PQInsert(FILE, fd);
    return fd;
}

int wrapper_close(int fd)
{
    int ret = 0;
    PQRemove(FILE, fd);
    ret = real_close(fd);
    return ret; 
}

3.1.3 调用支持资源泄漏检查的wrapper函数

有两种方式可使用支持资源泄漏检查的wrapper函数,一种是代码中之间调用open/close等函数对应的wrapper函数,另一种是借助gcc Xlink 的支持让标准的open/close函数“重定向”到wrapper_open/wrapper_close函数。显然,后面一种方法工做量最小、最优雅。具体的实现,能够参考前面关于Xlink的博文,下面列出了主要的几个步骤:code

  • gcc编译的flag中加入Xlinker改动

    Xlinker --wrap=open -Xlinker --undefined=wrapper_open Xlinker --wrap=close -Xlinker --undefined=wrapper_close
  • 新的头文件中加入声明

    typeof(open)         wrapper_open;
    typeof(close)        wrapper_close;
    typeof(open)         real_open;
    typeof(close)        real_close;

3.1.4 检查资源是否泄漏的函数

一般,在程序快要结束退出的时候,会释放资源,末了能够经过共享资源泄漏检查函数去检查是否正的有资源泄漏。这个函数的主要实现的示例以下:

int FileRes_check(void)
{
    if PQEmpty(FILE) {
        return PASS;
    } esle {
        PQDump(FILE);
        return FAIL;
    }
}

3.1.5 记录可能申请了被泄漏的资源代码的位置

根据上一篇博文中,关于如何获得程序堆栈中介绍的方法,咱们能够在申请共享资源的时候,具体来讲就是调用PQInsert(FILE, fd)时,取得当前程序的stack, 把堆栈信息和fd一块儿做为一项纪录插入到基于优先级队列、散列或者平衡二叉树实现的表中去。 一样,PQDump()除了打印没有关闭的文件句柄以外,还输出这个句柄对应打开时候的程序堆栈,根据这个堆栈信息,程序员可以定位到打开了这个没被关闭的句柄的代码的位置。而一般,这个句柄的关闭应该在它以后附近。

3.2 独享资源检查的实现

对独享资源死锁的检查的具体实现依赖的技术同上,差异就在死锁检查的逻辑和流程。读者能够自行尝试,等有时间了我也能够再来完善。

4. 总结

经过上面的这么多介绍能够看到,基于对共享资源和互斥资源使用特色的分析,咱们可以提出一种针对共享资源泄漏和独享资源死锁检查的通用原理。借助于合适的数据结构(二叉树、优先级队列或散列),基于 Xlinker方法、堆栈获取的API,咱们可以实现一种轻巧的、几乎不用改动已有代码的对开发者很是友好的资源检查功能。

相关文章
相关标签/搜索