Linux Kernel 模块内存泄露查找 (2)

在之前的一篇博文<<Linux
Kernel模块内存泄露的一种查找思路>>
中,我介绍了一种查找内核内存泄露的一种方法。这不才几个月,又有客户埋怨:使用了产品5天左右后,Suse服务器由于内存耗尽而Crash。O My God,不会吧,在我机器上跑的好好的哇(程序员常用名言 嘿嘿)。 那么就让我们一起来看看,苦逼的博主是如何确定问题并且找到问题的....

一. 确定问题

第一步,我们要做的是,确定这个问题和产品的Kernel模块有关系。首先根据客户描述,如果停止我们产品,则不会出现内存泄露问题。那确定问题和我们产品有关系,但是和用户态程序还是内核模块程序有关系呢?根据客户提供的Kernel Dump查看Slab占用3.6G。那么十有八九,是产品Kernel模块存在Memory Leak了。

++++++++++++++++++++++++++++++

crash> kmem -i

PAGES        TOTAL      PERCENTAGE

TOTAL MEM   981585       3.7 GB         ----

FREE    24987      97.6 MB    2% of TOTAL MEM

USED   956598       3.6 GB   97% of TOTAL MEM

SHARED       46       184 KB    0% of TOTAL MEM

BUFFERS       36       144 KB    0% of TOTAL MEM

CACHED       10        40 KB    0% of TOTAL MEM

SLAB   941424       3.6 GB   95% of TOTAL MEM

TOTAL SWAP  1048575         4 GB         ----

SWAP USED      527       2.1 MB    0% of TOTAL SWAP

SWAP FREE  1048048         4 GB   99% of TOTAL SWAP

++++++++++++++++++++++++++++++

但是某程序员,之前不是自信满满的说“在我机器上跑的好好的"嘛,那么就狠狠的打自己的脸吧!!!在打脸之前还是要恬不知耻的介绍下咱们的Kernel 模块:这个模块名叫KHM(Kernel
Hook Module)
,开源,对Linux中的文件操作进行Hook,并且传递文件信息给用户态进行文件扫描。

博主写了个脚本,不断的拷贝文件,模拟出大量的I/O操作,这样就会不断触发调用产品内核模块的Hook函数。在测试之前记录内存使用情况,先使用如下命令清除系统使用缓存:

SUSE11X64-001:~ # sync
SUSE11X64-001:~ # echo 3 > /proc/sys/vm/drop_caches 

然后记录内存使用情况,主要记录空闲内存和Slab使用内存:

+++++++++++++++++++++++++++++++++++++++++++++

SUSE11X64-001:~ # cat /proc/meminfo

MemTotal:        1989340 kB

MemFree:         1495368 kB

......

Slab:              37752 kB

......

+++++++++++++++++++++++++++++++++++++++++++++

然后等待3天(刚好过个周末~~~),使用如上同样方法查看当前空闲内存和Slab使用内存情况,最后发现3天内消耗大约300M内存,刚好约为Slab增长的内存。这样算下Memory leak Rate大概为4.2 M/hour.  也就是说,如果不是通过脚本模拟出大量的I/O操作,将会有更小的Memory Leak Rate,确实不易发现内存泄露。既然问题确定了,那么结下来就进行Memory Leak分析啦。

二. 问题分析

在对这个问题进行分析之前,我们分析下客户提供的Kernel Dump,Slab中哪种类型的Cache占用了太多的内存:sock_inode_cache占用了大约1.8G内存, dentry大约占用了700多M内存。

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

crash> kmem -s

CACHE            NAME                 OBJSIZE  ALLOCATED     TOTAL  SLABS  SSIZE

......

ffff880138431300 sock_inode_cache         640    2842421   2842524 473754     4k

......

ffff880138c00e00 dentry                   192    3769490   3769880 188494     4k

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

sock_inode_cache在内核中存储socket的内核结构,而dentry则对应文件或者目录在内核中的数据结构,如果你和我一样,对Linux的内核还没有特别精通的情况下,那么首要的怀疑目标就是dentry。在内核模块中会对文件的dentry进行访问,那么如何引起内存泄露的呢?这时有以下两个怀疑的思路:

(1) 采用kmalloc等api申请内存空间然后没有释放;

(2) 在对dentry引用访问后,没有对其引用计数进行释放,比如调用dget之后,并没有相应的调用dput.

然后通过Code Review排除了情况(1),但是针对情况(2)也进行了查看,发现在访问dentry后,都调用了dput减少一次引用计数。这个问题一直深深的困扰着我,一个星期以来都不愿意再看这个问题了,可是问题总归要解决的啊??也想了一些方法,比如使用kmemleak?但是得重新编译所有的Suse内核源码,并且不一定能够很清楚的查询到Memory Leak的原因,鉴于我们产品内核模块的代码量不是很大,最终决定,再一次进行Code
Review。2天半的时间,功夫不负有心人,终于找到了根本原因!

三. 根本原因

程序执行流程如下:

(1) 根据文件fd,获取file对象,从file对象中获取path对象,并使用path指针pPath记录path对象地址(path对象中包扩了dentry和vfsmount成员指针)。

(2)程序中需对dentry和vfsmount进行访问,于是采用path_get(pPath)对引用计数加一

(3)调用系统中的原始的close,来关闭文件

(4)进行一系列的操作后,采用path_put(pPath)对dentry和vfsmount引用计数减一

问题就出在第(3)步和第(4)步上:如果只有一个进程对文件打开并进行了访问,然后关闭文件,则进入我们产品的Hook函数,当进入第三步的时候,调用系统原始的close,内核中将进行如下调用过程:close->filp_close->fput->__fput.
可以看到在fput中,如果当前file对象的引用计数只为1的时候,才调用__fput.

void fput(struct file *file)
{
        if (atomic_long_dec_and_test(&file->f_count))
                __fput(file);
}

一般情况下file对象此时引用计数为1(例外比如一个进程打开文件并且fork),调用__fput,注意其中会将其dentry和mnt指针设置为NULL。

static void __fput(struct file *file)
{
        struct dentry *dentry = file->f_path.dentry;
        struct vfsmount *mnt = file->f_path.mnt;
        struct inode *inode = dentry->d_inode;

        might_sleep();

        fsnotify_close(file);
        /*
         * The function eventpoll_release() should be the first called
         * in the file cleanup chain.
         */
        eventpoll_release(file);
        locks_remove_flock(file);

        if (unlikely(file->f_flags & FASYNC)) {
                if (file->f_op && file->f_op->fasync)
                        file->f_op->fasync(-1, file, 0);
        }
        if (file->f_op && file->f_op->release)
                file->f_op->release(inode, file);
        security_file_free(file);
        ima_file_free(file);
        if (unlikely(S_ISCHR(inode->i_mode) && inode->i_cdev != NULL &&
                     !(file->f_mode & FMODE_PATH))) {
                cdev_put(inode->i_cdev);
        }
        fops_put(file->f_op);
        put_pid(file->f_owner.pid);
        file_sb_list_del(file);
        if ((file->f_mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ)
                i_readcount_dec(inode);
        if (file->f_mode & FMODE_WRITE)
                drop_file_write_access(file);
        file->f_path.dentry = NULL;
        file->f_path.mnt = NULL;
        file_free(file);
        dput(dentry);
        mntput(mnt);
}

因为在第(2)步我们对dentry和mnt进行了引用计数加1(此时引用计数为2),那么在__fput中调用dput和mntput只会对其引用计数减一,但使用内存并不会进行释放。而理论上应该在第(4)步进行完我们定义的操作之后,对其进行释放。可是!!!我们在第(4)步的时候调用了path_put(pPath)去进行释放,可这时候因为之前调用了原始的close,path中的dentry和mnt指针早已被设置为NULL,所以在第(4)步的时候,dentry和mnt并没有进行引用计数减一,进而也没有释放内存,从而造成了内核的Memory
Leak。

虽然产品的KHM是开源的,但是为了避嫌,在博文中,并没有很详细的说明KHM的工作流程,也许说明可以将第三部分描述的更加清晰。不过有问题或者错误,还是欢迎博友提出和讨论。^_^

Linux Kernel 模块内存泄露查找 (2)

时间: 2024-10-08 10:34:37

Linux Kernel 模块内存泄露查找 (2)的相关文章

linux kernel 模块多文件编译

/*************************************************************************** * linux kernel 模块多文件编译 * 声明: * 本文主要是记录在调试驱动的阶段,我们可能会更倾向于使用模块插入的方式 * 来进行驱动调试,这样可以大大缩短调试时间.之前在调试gt9xx Touch的时候也 * 是采用这种方式,这里还是记录一下,日后方便查找. * * 2016-2-2 深圳 南山平山村 曾剑锋 **********

Linux下C++内存泄露检测工具

下载安装:http://blog.csdn.net/wanglin754/article/details/7194145 下载地址:http://www.valgrind.org/downloads/current.html#current 安装valgrind tar jxvf valgrind-3.7.0.tar.bz2             注意这里的参数里加了j,表示有bz2属性 cd valgrind-3.7.0 ./configure make make install make

android中的内存泄露查找与常见的内存泄露案例分析

常见的内存泄露查找方法请参见:http://hukai.me/android-performance-patterns/ 这篇文章是google发布的android性能优化典范示例,对于渲染.内存GC与电量消耗都做了好的示范. 这里我总结了下,android中常见的内存泄露 1.类中调用registerReceiver后未调用unregisterReceiver(). 在调用registerReceiver后,若未调用unregisterReceiver,其所占的内存是相当大的. 这种情况常见于

CentOS启动流程、Grub legacy配置、linux kernel模块管理、伪文件系统介绍

写在前面: 博客书写牢记5W1H法则:What,Why,When,Where,Who,How. 本篇主要内容: ● 启动相关基础概念汇总 ● 启动流程 ● init程序类型     /etc/rc.d/rc     chkconfig     /etc/rc.d/rc.sysinit ● GRUB legacy     命令行接口     配置文件 ● Linux Kernel     内核模块查看与管理         lsmod         modinfo         modprob

Linux C 编程内存泄露检测工具(一):mtrace

前言 所有使用动态内存分配(dynamic memory allocation)的程序都有机会遇上内存泄露(memory leakage)问题,在Linux里有三种常用工具来检测内存泄露的情況,包括: mtrace dmalloc memwatch 1. mtrace mtrace是三款工具之中是最简单易用的,mtrace是一个C函數,在<mcheck.h>里声明及定义,函数原型为: void mtrace(void); 其实mtrace是类似malloc_hook的malloc handle

linux下java内存泄露定位jstat+jmap+jhat

1.   jstat -gcutil 14331 3s   3秒一次监控内存回收情况 S0     S1     E      O      P     YGC     YGCT    FGC    F 0.00   9.04  26.16  61.43  99.52    833    6.973    14 0.00   9.04  26.16  61.43  99.52    833    6.973    14 0.00   9.04  26.16  61.43  99.52    83

Linux kernel 模块 hello 测试

原文链接:https://www.cnblogs.com/nerohwang/p/3621316.html hello.c 文件: #include <linux/kernel.h> /*Needed by all modules*/ #include <linux/module.h> /*Needed for KERN_* */ #include <linux/init.h> /* Needed for the macros */ MODULE_LICENSE(&qu

Linux kernel模块管理相关详解

一.Linux内核模块化设计 1.Linux内核设计:单内核.模块化(动态装载和卸载) (1 )Linux:单内核设计,但充分借鉴了微内核体系的设计的优点:为内核引入了模块化机制: (2) 内核的组成部分: kernel:内核核心,一般为bzImage格式,通常位于/boot目录,名称为vmlinuz-VERSION-release: 当系统启动之后该文件就不在使用,因为已经加载到内存,放置/boot下方便管理 kernel object:内核模块,一般放置于/lib/modules/VERSI

Linux下检测内存泄露的工具 valgrind

参考:http://www.cnblogs.com/sunyubo/archive/2010/05/05/2282170.html 几乎是照抄参考过来的,只不过后面自己调试一下代码. 这里主要介绍Valgrind的一些简单用法.更多详细的使用方法可以访问valgrind的主页:http://www.valgrind.org Valgrind是Julian Seward的作品.Valgrind是运行在Linux上一套基于仿真技术的程序调试和分析工具,它包含一个内核,一个软件合成的CPU,和一系列的