智汇华云|内核调试分享:Linux内核内存泄漏了

来源:华云数据公众号 更新时间:2020-03-23
近期华云数据公有云上线了安卓云,客户测试反馈经常内存跑满了,导致安卓程序被杀死。但是我们内部运行几周并没有出现问题,询问客户得知是打开某音APP播放视频,什么也不用操作,逐渐内存就被耗光了。基于此,本期“智汇华云”邀请到华云数据计算网络部总监仇大玉为大家带来内核调试分享,带大家一同解密Linux内核内存泄漏。

/upload/image/20200323/339ba714d42e92897c372eaa5d7674e7.jpg

近期华云数据公有云上线了安卓云,客户测试反馈经常内存跑满了,导致安卓程序被杀死。

但是我们内部运行几周并没有出现问题,询问客户得知是打开某音APP播放视频,什么也不用操作,逐渐内存就被耗光了。

基于此,本期“智汇华云”邀请到华云数据计算网络部总监仇大玉为大家带来内核调试分享,带大家一同解密Linux内核内存泄漏。

本期嘉宾

仇大玉 - 副本.jpg

华云数据计算网络部总监 仇大玉

让我们先来查看free:

1.png

6G的内存(实际5.8G),free只有210M了,通过统计SLAB的内存量:echo `cat /proc/slabinfo |awk 'BEGIN{sum=0;}{sum=sum+$3*$4;}END{print sum/1024/1024}'` MB

一查slabinfo发现slab就占据了1个多G,在部分3G的手机中,slab能高达1.9G。

太恐怖,再看/proc/meminfo里面确实slab占用很多(一下是在另外一台云手机中操作)。

2.png

尝试echo 3 > /proc/sys/vm/drop_caches 释放cache,发现基本没有用,一查在看到基本都是SUnreclaim,根本不能被释放,很显然出现了内核内存泄漏。

同时查看slabinfo: cat /proc/slabinfo

/upload/image/20200323/5911f39907063d2b71cb2e3087c2e225.png

kmalloc-256占用1.2G左右,128也占用了600M左右。

尝试打开slab trace: echo 1 > /sys/kernel/slab/kmalloc-256/trace,竟没有成功, cat /sys/kernel/slab/kmalloc-256/trace 依然是0.

没办法,只能重新编译内核,打开SLAB的debug以及内核做内存泄漏检测的开关KMEMLEAK:

+CONFIG_SLUB_DEBUG=y

+CONFIG_SLUB_DEBUG_ON=y

+DEBUG_KMEMLEAK=y

重新编译安卓内核(我们安卓内核是基于upstream内核自己定制的,不是原生安卓的内核)

重新打开slab trace: echo 1 > /sys/kernel/slab/kmalloc-256/trace

终于可以打开了,然后打开某音app开始播放视频。大概一分钟后,安卓里面 dmesg得到很多debug的Trace,命名为dmesg_218,

然后便于寻找:

grep "TRACE kmalloc-256 alloc" dmesg_218 | awk '{print $6}' | sort > alloc.txt

grep "TRACE kmalloc-256 free" dmesg_218 | awk '{print $6}' | sort > free.txt

再打开这两个文件对比:vim alloc.txt free.txt -O2

/upload/image/20200323/459be1f26a61c305ff6dde5236986eb8.png

发现alloc要明显多于free,有些例如黄色显示部分alloc 4次,只free了3次。很明显是有内存没有被释放,在dmesg里找到这个地址,查看是哪里alloc的没有被释放:

/upload/image/20200323/1575482bd65a80fe856535fe621400ee.png

追踪到最后发现是这边,只有alloc没有对应的free。

暂时找到问题了,通过刚才打开的kmemleak:cat /sys/kernel/debug/kmemleak确认一下:

/upload/image/20200323/3d0bd48e6f6f4332e8ac4edbe43c6bfe.png

追踪到大量的都是sw_sync_ioctl这个函数中申请的都没有被释放。至此形式明朗了许多。

找到内核中这个函数:vim ./drivers/dma-buf/sw_sync.c

调用链是sw_sync_ioctl()-->sw_sync_ioctl_create_fence()-->sync_pt_create()。

在sync_pt_create函数中调用了pt = kzalloc(sizeof(*pt), GFP_KERNEL);而kzalloc会调用slab分配器分配内存。然后查找这个pt在哪释放了。通篇源码中没有找到,但是找到了:

timeline_fence_release()中调用了fence_free(fence),而fence正式pt里面的对像,怎么回事?没有释放pt,但是释放了fence?

看下pt结构体:

/**

* struct sync_pt - sync_pt object

* @base: base fence object

* @link: link on the sync timeline's list

* @node: node in the sync timeline's tree

*/

struct sync_pt {

struct fence base;

struct list_head link;

struct rb_node node;

};

发现没有,base地址就是pt的地址,但是承载的对象不一样。继续追踪,发现timeline_fence_release是static const struct fence_ops timeline_fence_ops的release函数实现,也就是fence->ops中的release,查遍源码:

/upload/image/20200323/ab6a4c5740edcabdf0ac2e8181450083.png

而fence_relase只有fence_put调用了:

/upload/image/20200323/c2597fe54fa15d9e314d9e030ad060b5.png

查到此,发现fence_put会在fence的refcount减一后如果为0调用释放内存。

回到sw_sync这个驱动,竟然没有地方调用fence_put(其实是有一个地方的就是在创建pt后创建sync_file如果创建失败会去调用fence_put),这也就奇怪为何内存泄漏了。

改动方案(fence主要用户是安卓的图形相关HWCompose,不再赘述),这边在内核端每个fence被signal后显示调用fence_put,验证后没有问题,没有内存泄漏了。

在线咨询
400-808-4000
免费试用