专栏首页一个程序员的修炼之路Linux Kernel 模块内存泄露查找 (2)

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 ++++++++++++++++++++++++++++++

但是某程序员,之前不是自信满满的说“在我机器上跑的好好的"嘛,那么就狠狠的打自己的脸吧!!!产品实现了一个内核级别的I/O 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。

本文分享自微信公众号 - 一个程序员的修炼之路(CoderStudyShare),作者:iceking

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2015-08-07

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Linux Kernel模块内存泄露的一种查找思路

    最近有个客户报了一个问题:如果运行我们的产品,则每天将会增长大概30M的内存,大概4个多月内存就会耗尽。和大多数程序员的反应一样,“不会吧,在其他客户机...

    河边一枝柳
  • 解Bug之路-记一次JVM堆外内存泄露Bug的查找 顶

    JVM的堆外内存泄露的定位一直是个比较棘手的问题。此次的Bug查找从堆内内存的泄露反推出堆外内存,同时对物理内存的使用做了定量的分析,从而实锤了Bug的源头。笔...

    无毁的湖光-Al
  • 今咱们来聊聊JVM 堆外内存泄露的BUG是如何查找的

    JVM的堆外内存泄露的定位一直是个比较棘手的问题。此次的Bug查找从堆内内存的泄露反推出堆外内存,同时对物理内存的使用做了定量的分析,从而实锤了Bug的源头。笔...

    美的让人心动
  • 今咱们来聊聊JVM 堆外内存泄露的BUG是如何查找的前言内存泄露Bug现场查找线索总结

    美的让人心动
  • 解Bug之路-记一次JVM堆外内存泄露Bug的查找

    用户1263954
  • 解Bug之路-记一次JVM堆外内存泄露Bug的查找

    JVM的堆外内存泄露的定位一直是个比较棘手的问题。此次的Bug查找从堆内内存的泄露反推出堆外内存,同时对物理内存的使用做了定量的分析,从而实锤了Bug的源头。笔...

    呆呆
  • 安全漏洞公告

    1 PHP FPM 'php-fpm.conf.in'本地权限提升漏洞 PHP FPM 'php-fpm.conf.in'本地权限提升漏洞发布时间:2014-0...

    安恒信息
  • K8S 问题排查:cgroup 内存泄露问题

    这篇文章的全称应该叫:[在某些内核版本上,cgroup 的 kmem account 特性有内存泄露问题],如果你遇到过 pod 的 cannot alloca...

    YP小站
  • 跨平台c开发库tbox:内存库使用详解

    TBOX的内存管理模型,参考了linux kernel的内存管理机制,并在其基础上做了一些改进和优化。

    ruki
  • CNNVD最新漏洞

    今日CNNVD共发布安全漏洞48个,更新安全漏洞2个。主要影响厂商为美国Google(22个)、美国IBM(6个)、美国Linux(4个),主要影响产品为And...

    企鹅号小编
  • Linux中删除文件,磁盘空间未释放问题追踪

    在客户使用我们产品后,发现一个问题:在删除了文件后,磁盘空间却没有释放。是有进程在打开这个文件,还是其他情况?我们一起来看看一下两个场景

    河边一枝柳
  • 句柄泄露问题追踪

    无论是在编写Windows程序还是Linux程序,都可能存在句柄泄露的问题。在Linux中一般来说一个进程的fd使用是有上限的,可以使用ulimit命令进行上限...

    河边一枝柳
  • 当Linux用尽内存

    也许你很少面临这一情况,但是一旦如此,你一定知道出什么错了:可用内存不足或者说内存用尽(OOM)。结果非常典型:你不能再分配内存,内核会杀掉一个任务(一般是正在...

    一见
  • 分享Linux内存占用几个案例

    最近一台 CentOS 服务器,发现内存无端损失了许多,free 和 ps 统计的结果相差十几个G,非常奇怪,后来Google了许久才搞明白。

    YP小站
  • 2019预备BAT大厂Android研发岗秋招必问30+道高级面试题(附详细答案解析)

    如今安卓开发不像前几年那么热门,但是高级人才依然紧缺,大家看着这句话是不是很熟悉,因为 web 高级人才也紧缺,c++ 高级人才一样紧缺,那么到了人工智能时代,...

    Android技术干货分享
  • ROP-Ret2libc学习

    ​ 针对于程序的逻辑了解我们可以编写两个模块,一个是程序自身的代码模块,另一个是共享对象模块。以此来学习动态链接的程序是如何进行模块内的函数调用和...

    ly0n
  • 安全漏洞公告

    1 Linux Kernel 'linux-image-3.2.0-4-5kc-malta'软件包拒绝服务漏洞Linux Kernel 'linux-image...

    安恒信息
  • 诊断修复 TiDB Operator 在 K8s 测试中遇到的 Linux 内核问题

    Kubernetes(K8s)是一个开源容器编排系统,可自动执行应用程序部署、扩展和管理。它是云原生世界的操作系统。 K8s 或操作系统中的任何缺陷都可能使用户...

    PingCAP
  • valgrind测试报告分析

    valgrind输出结果会报告5种内存泄露,"definitely lost", "indirectly lost", "possibly lost", "st...

    yzh

扫码关注云+社区

领取腾讯云代金券