一. 问题描述
最近有个客户报了一个问题:如果运行我们的产品,则每天将会增长大概30M的内存,大概4个多月内存就会耗尽。和大多数程序员的反应一样,“不会吧,在其他客户机器上都跑的好好的啊,从来都没有遇到过这样的问题”。最后的结果,也往往告诉程序员一个铁的事实:你的程序确实出问题了!
如果你某天发现通过“Free”查看内存几乎耗尽,但通过top/ps命令却看不出来用户态应用程序占用太多的内存空间,那么内核模块可能发生了内存泄露。
二. SLAB简介
SLAB是Linux内核中按照对象大小进行分配的内存分配器。
通过SLAB的信息来查看内核模块占用的内存空间,提供了三种方式(怎么有点像茴香豆的几种写法....囧):
1. 查看meminfo文件
++++++++++++++++++++++++++++++++++++++++++++++++++++++
[root@RHEL5_64 ~]# cat /proc/meminfo | grep Slab
Slab: 121588 kB
++++++++++++++++++++++++++++++++++++++++++++++++++++++
2. 查看slabinfo文件
++++++++++++++++++++++++++++++++++++++++++++++++++++++
[root@RHEL5_64 ~]# cat /proc/slabinfo
slabinfo - version: 2.1
# name <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail>
cifs_small_rq 31 32 448 8 1 : tunables 54 27 8 : slabdata 4 4 0
cifs_request 5 5 16512 1 8 : tunables 8 4 0 : slabdata 5 5 0
cifs_oplock_structs 0 0 64 59 1 : tunables 120 60 8 : slabdata 0 0 0
......
size-32 29904 29904 32 112 1 : tunables 120 60 8 : slabdata 267 267 0
kmem_cache 156 156 2688 1 1 : tunables 24 12 8 : slabdata 156 156 0
++++++++++++++++++++++++++++++++++++++++++++++++++++++
3. 通过slabtop命令查看 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
[root@RHEL5_64 ~]# slabtop
Active / Total Objects (% used) : 630313 / 712770 (88.4%)
Active / Total Slabs (% used) : 30213 / 30214 (100.0%)
Active / Total Caches (% used) : 105 / 157 (66.9%)
Active / Total Size (% used) : 104775.55K / 113499.95K (92.3%)
Minimum / Average / Maximum Object : 0.02K / 0.16K / 128.00K
OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME
494040 419833 84% 0.09K 12351 40 49404K buffer_head
71856 71832 99% 0.21K 3992 18 15968K dentry_cache
40080 40076 99% 0.74K 8016 5 32064K ext3_inode_cache
29904 29904 100% 0.03K 267 1121068K size-32
......
++++++++++++++++++++++++++++++++++++++++++++++++++++++
以上三种方法,一般查看slabinfo文件就足以,如果发现slabinfo中占用内存过大,那基本可以断定,内核模块出现了内存泄露了,那么我们就要寻找哪里出现了内存泄露呢?
三. 内核模块内存泄露的分析
起初也怀疑是不是因为cache占用了过多的内存,可清楚缓存后内存依旧很大。那摆在面前的只有两条路了:
1. 产品在服务启动的时候,会Load一个我们的Kernel模块,对这个Kernel模块进行Code Review,比如搜索kmalloc的地方,然后查看是否进行了释放。
2. 使用Kdump产生kernel Dump,然后使用Crash对其进行分析。
第一种方法我想程序员都会放在最后一步计划中吧,嘿嘿。于是让客户触发了一个Kernel Dump,然后对其进行了分析,步骤如下:
1. 使用crash加载vmcore (首先要安装相应kernel的Debug Info, Redhat的要有合作账号才能下载到)
++++++++++++++++++++++++++++++++++++++++++++++++++++++
[root@RHEL5_64 analysis]# crash vmcore vmlinux
++++++++++++++++++++++++++++++++++++++++++++++++++++++
2. 查看Slab中那种类型的cache占用内存比较多,名为size-32的cache中分配了208177857个对象,每个对象32个字节,也就是占用了将近6.7G的内存。
++++++++++++++++++++++++++++++++++++++++++++++++++++++
crash> kmem -s
ffff810107e4a040 size-32 32 208177857 208177984 1858732 4k
++++++++++++++++++++++++++++++++++++++++++++++++++++++
3. 接着查看Slab size-32中分配的对象地址信息,由于size-32的对象巨多无比,于是将输出的内容重定向到“kmem_size32_slab.txt”:
++++++++++++++++++++++++++++++++++++++++++++++++++++++
crash> kmem -S size-32 > kmem_size32_slab.txt
++++++++++++++++++++++++++++++++++++++++++++++++++++++
4. 然后我们查看重定向到"kmeme_size32_slab.txt"中的对象地址信息,并随机选择一个已分配的对象地址“ffff81021927a780”:
++++++++++++++++++++++++++++++++++++++++++++++++++++++
……
SLAB MEMORY TOTAL ALLOCATED FREE
ffff81021927a000 ffff81021927a200 112 112 0
FREE / [ALLOCATED]
[ffff81021927a200]
……
[ffff81021927a760]
[ffff81021927a780]
[ffff81021927a7a0]
……
……
++++++++++++++++++++++++++++++++++++++++++++++++++++++
5. 这时候再利用刚才对象的指针,查找对象附近的内存信息,可以查看到很多"sbin/*agt","t.point.*"等字符串信息:
++++++++++++++++++++++++++++++++++++++++++++++++++++++
crash> rd -a ffff81021927a780 10000
…...
ffff81021927a824: sbin/*agt/
ffff81021927a832: '
ffff81021927a838: v
ffff81021927a840: tmp
ffff81021927a844: s.*/
ffff81021927a849: }
ffff81021927a850: (
ffff81021927a852: '
ffff81021927a858: q
ffff81021927a860: tmp
ffff81021927a864: @Vv*/
ffff81021927a86a: t.point.*
ffff81021927a878: a
ffff81021927a880: tmp
ffff81021927a884: @Vv*/
ffff81021927a88a: t.point.*
ffff81021927a898: U
ffff81021927a8a0: usr
ffff81021927a8a4: sbin/*agt/
……
++++++++++++++++++++++++++++++++++++++++++++++++++++++
6. 以这些对象中存储的字符串为线索,结合产品内核模块中的代码:kmalloc之后存储的字符串哪些可能含有"sbin/*agt","t.point.*",并且最终没有释放。这样很快就定位到了问题的根本原因了。
以上的步骤也只是提供了一种查找产品内核模块内存泄露的一种思路,并且在第4,5步骤需要不断的进行尝试,在这个例子中幸运的是,泄露的那些内存存储的都是些产品中需要用到的字符串,比较容易定位到问题。如果存储的是内核对象的一些地址,可能需要更进一步的分析,并且这些分析都要建立在对自己的产品比较熟悉的基础上。
在解决问题的过程中,也找到了一些资料供大家分享:
Resolving Memory Leaks In Linux Kernel