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

简介:

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

1. 背景

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

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

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

2. 原理

针对上面的三个例子使用中的资源,我们可以归纳成两类:数量有限的共享资源,比如上面空间有限的内存和数量有限的文件句柄;需要独占的互斥资源,比如上面例子中提到的锁。 下面就分别针对这两种情况,分别展开分析。

2.1 对共享资管的检查

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

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

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的博文,下面列出了主要的几个步骤:

  • 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,我们能够实现一种轻巧的、几乎不用改动已有代码的对开发者非常友好的资源检查功能。














本文转自存储之厨51CTO博客,原文链接:http://blog.51cto.com/xiamachao/2065841 ,如需转载请自行联系原作者


相关文章
|
2月前
|
Linux
linux 常用内存检查命令
linux 常用内存检查命令
41 0
|
2月前
|
Rust 安全 编译器
Rust中的生命周期与借用检查器:内存安全的守护神
本文深入探讨了Rust编程语言中生命周期与借用检查器的概念及其工作原理。Rust通过这些机制,在编译时确保了内存安全,避免了数据竞争和悬挂指针等常见问题。我们将详细解释生命周期如何管理数据的存活期,以及借用检查器如何确保数据的独占或共享访问,从而在不牺牲性能的前提下,为开发者提供了强大的内存安全保障。
|
6月前
|
缓存 Linux
百度搜索:蓝易云【检查 Linux 系统内存使用量是否耗尽?这5个命令堪称绝了!详解!】
通过使用以上这五个命令,你可以全面了解Linux系统的内存使用情况,从而判断是否存在内存耗尽的问题。这些命令提供了不同层次和角度的内存信息,帮助你定位和解决与内存相关的性能问题。
90 0
|
存储 缓存 监控
如何检查 Linux 内存使用量是否耗尽?这5个命令堪称绝了!
如何检查 Linux 内存使用量是否耗尽?这5个命令堪称绝了!
166 0
|
Ubuntu
UBUNTU设置环境变量MALLOC_CHECK_=1检查内存
UBUNTU设置环境变量MALLOC_CHECK_=1检查内存
111 0
使用valgrind检查内存越界
使用valgrind检查内存越界
192 0
|
Linux
LINUX检查一个进程内存增长的脚本
LINUX检查一个进程内存增长的脚本
100 0
|
存储 Web App开发 JSON
检查自己的代码是否存在内存泄露
造成内存泄露的根本原因就是我们写的代码中存在某些对象长期占用内存,得不到释放,且这个对象占用的内存会逐步增加,导致 v8 无法回收,从而造成的服务的异常和不稳定,甚至是服务的中断和崩溃。
249 0
检查自己的代码是否存在内存泄露
|
Windows
svchost.exe占用内存过高--(windows update检查更新时一直处于正在检查)
学习.exe占用内存过高--(windows update检查更新时一直处于正在检查)。
189 0
|
缓存 监控 网络协议
Linux:常用性能检查命令(内存、CPU 、网络、磁盘、Java应用)
Linux:常用性能检查命令(内存、CPU 、网络、磁盘、Java应用)
837 0
Linux:常用性能检查命令(内存、CPU 、网络、磁盘、Java应用)