近期华云数据公有云上线了安卓云,客户测试反馈经常内存跑满了,导致安卓程序被杀死。
但是我们内部运行几周并没有出现问题,询问客户得知是打开某音APP播放视频,什么也不用操作,逐渐内存就被耗光了。
基于此,本期“智汇华云”邀请到华云数据计算网络部总监仇大玉为大家带来内核调试分享,带大家一同解密Linux内核内存泄漏。
本期嘉宾 华云数据计算网络部总监 仇大玉 让我们先来查看free: 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占用很多(一下是在另外一台云手机中操作)。 尝试echo 3 > /proc/sys/vm/drop_caches 释放cache,发现基本没有用,一查在看到基本都是SUnreclaim,根本不能被释放,很显然出现了内核内存泄漏。 同时查看slabinfo: cat /proc/slabinfo 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 发现alloc要明显多于free,有些例如黄色显示部分alloc 4次,只free了3次。很明显是有内存没有被释放,在dmesg里找到这个地址,查看是哪里alloc的没有被释放: 追踪到最后发现是这边,只有alloc没有对应的free。 暂时找到问题了,通过刚才打开的kmemleak:cat /sys/kernel/debug/kmemleak确认一下: 追踪到大量的都是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,查遍源码: 而fence_relase只有fence_put调用了: 查到此,发现fence_put会在fence的refcount减一后如果为0调用释放内存。 回到sw_sync这个驱动,竟然没有地方调用fence_put(其实是有一个地方的就是在创建pt后创建sync_file如果创建失败会去调用fence_put),这也就奇怪为何内存泄漏了。 改动方案(fence主要用户是安卓的图形相关HWCompose,不再赘述),这边在内核端每个fence被signal后显示调用fence_put,验证后没有问题,没有内存泄漏了。