Slab allocator是Linux kernel的内存分配机制,各内核子系统、模块、驱动程序都可以使用,但用完应该记得释放,忘记释放就会造成“内存泄露”(memory leak)。如果导致泄露的代码使用率很低倒也罢了,若是使用率很高的话,系统的内存会被迅速耗尽。
在以下案例中,132 GB 内存,仅剩21 GB空闲,还有16 GB的交换区被用掉了,显然内存使用相当紧张,而内存的主要去向是slab,slab用了89 GB,其中不可回收的部分(SUnreclaim)就占了88 GB:
1 2 3 4 5 6 7 8 9 10 11 |
$ less /proc/meminfo MemTotal: 132124108 kB MemFree: 21361420 kB ... SwapTotal: 33554428 kB SwapFree: 17019448 kB ... Slab: 89078888 kB SReclaimable: 515108 kB SUnreclaim: 88563780 kB ... |
再细看/proc/slabinfo,发现“size-4096”占用了84 GB之多(21720567*4096):
1 2 3 4 5 6 |
$ less /proc/slabinfo slabinfo - version: 2.1 # name <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail> ... size-4096 21720555 21720567 4096 1 1 : tunables 24 12 8 : slabdata 21720555 21720567 0 ... |
通常Slab的名字就表明了其用途,比如”inode_cache”、”dentry”什么的,根据发生泄露的slab cache的名字,大致就知道是哪个子系统或模块的问题,然而本例比较复杂,因为从”size-4096″的名称完全看不出slab cache的用途。
即便从名字知道了是哪个子系统的问题,为了进一步定位故障点,我们还要看到具体是哪些函数、哪些代码在分配内存才行。要如何诊断此类问题呢?
首先取决于kernel用的是slab还是slub,slab其实是一个统称,Linux kernel自2.6.23之后就已经从Slab进化成Slub了,它自带原生的诊断功能,比Slab更方便。判断kernel是否在使用slub有一个简单的方法,就是看/sys/kernel/slab目录是否存在,如果存在的话就是slub,否则就是slab。SLUB的故障诊断请参考以下文章:如何诊断SLUB问题
SLAB
如果是slab的话,有两种常见方法:一是利用debug kernel的slab leak辅助功能,二是利用systemtap等工具。参见https://access.redhat.com/solutions/358933
使用kernel的DEBUG_SLAB_LEAK功能
这需要kernel编译的时候打开了”CONFIG_DEBUG_SLAB_LEAK”选项才行,默认是没打开的。
对RHEL或CentOS来说,debug kernel打开了此编译选项,可以安装名为kernel-debug-*的rpm软件包,然后重启系统并选择此debug kernel即可。
完成后/proc目录下会出现一个名为slab_allocators的文件,里面会记录类似如下的slab分配的信息,注意观察是什么代码在分配slab,有助于找到可疑的泄漏点。缺点是只记录了直接调用的函数,没有完整的backtrace:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
buffer_head: 2555 alloc_buffer_head+0x20/0x75 mm_struct: 9 mm_alloc+0x1e/0x42 mm_struct: 20 dup_mm+0x36/0x370 vm_area_struct: 384 dup_mm+0x18f/0x370 vm_area_struct: 151 do_mmap_pgoff+0x2e0/0x7c3 fs_cache: 8 copy_fs_struct+0x21/0x133 fs_cache: 29 copy_process+0xf38/0x10e3 files_cache: 30 alloc_files+0x1b/0xcf signal_cache: 81 copy_process+0xbaa/0x10e3 sighand_cache: 77 copy_process+0xe65/0x10e3 anon_vma: 241 anon_vma_prepare+0xd9/0xf3 size-2048: 1 add_sect_attrs+0x5f/0x145 size-2048: 2 journal_init_revoke+0x99/0x302 |
使用systemtap
除了使用debug kernel之外,还有个方法就是用systemtap,对内核适当的位置植入探针,有助于找到可疑的slab分配,这需要对内核有一定的了解才行。
普通的slab cache是通过kmem_cache_alloc来分配的,可以用现成的systemtap probe vm.kmem_cache_alloc进行观测。但是在本例中不适用,因为本例中”size-4096″属于slab里的general purpose cache,是供kmalloc()使用的,所以systemtap应该针对kmalloc()进行探测,这里有一个现成的脚本 “kmalloc-top“,它的原理是对__kmalloc()下探针,记录backtraces,因为__kmalloc是实现kmalloc()的核心函数,有的代码会直接调用__kmalloc,所以探测它而不是kmalloc()才不会有遗漏。以上的脚本没有记录kmalloc的size,所以我修改了一下,加上了kmalloc size,修改过的内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#The systemtap script that instruments the kmalloc $script=" global kmalloc_stack probe kernel.function(\"__kmalloc\") { kmalloc_stack[\$size, backtrace()]++ } probe timer.ms(100), end { foreach ([size, stack] in kmalloc_stack) { printf(\"\\n\") printf(\" kmalloc size %d\\n\", size) print_syms(stack) printf(\"\\n\") printf(\"%d\\n\", kmalloc_stack[size, stack]) } delete kmalloc_stack } "; |
以root身份执行:
1 |
# ./kmalloc-top -o '--all-modules' > /tmp/kmtop.out |
间隔一段时间再ctrl-c退出,看到结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
... This path seen 1021 times: kmalloc size 4096 0xffffffff811783e0 : __kmalloc+0x0/0x230 [kernel] 0xffffffffa022401e : 0xffffffffa022401e [sisips] 0xffffffffa024d46f : 0xffffffffa024d46f [sisips] 0xffffffffa023b763 : 0xffffffffa023b763 [sisips] 0xffffffffa022abca : 0xffffffffa022abca [sisips] 0xffffffffa022d51a : 0xffffffffa022d51a [sisips] 0xffffffff81290745 : _atomic_dec_and_lock+0x55/0x80 [kernel] 0xffffffff81193611 : __fput+0x1a1/0x210 [kernel] 0xffffffff810e884e : __audit_syscall_exit+0x25e/0x290 [kernel] 0xffffffff8100b0d2 : system_call_fastpath+0x16/0x1b [kernel] This path seen 1021 times: kmalloc size 4096 0xffffffff811783e0 : __kmalloc+0x0/0x230 [kernel] 0xffffffffa022401e : 0xffffffffa022401e [sisips] 0xffffffffa0224a32 : 0xffffffffa0224a32 [sisips] 0xffffffffa022abac : 0xffffffffa022abac [sisips] 0xffffffffa024f5c8 : 0xffffffffa024f5c8 [sisips] 0xffffffffa022d51a : 0xffffffffa022d51a [sisips] 0xffffffff81290745 : _atomic_dec_and_lock+0x55/0x80 [kernel] 0xffffffff81193611 : __fput+0x1a1/0x210 [kernel] 0xffffffff810e884e : __audit_syscall_exit+0x25e/0x290 [kernel] 0xffffffff8100b0d2 : system_call_fastpath+0x16/0x1b [kernel] This path seen 853 times: kmalloc size 4096 0xffffffff811783e0 : __kmalloc+0x0/0x230 [kernel] 0xffffffffa022401e : 0xffffffffa022401e [sisips] 0xffffffffa0222c30 : 0xffffffffa0222c30 [sisips] 0xffffffffa024d46f : 0xffffffffa024d46f [sisips] 0xffffffffa024dd49 : 0xffffffffa024dd49 [sisips] 0xffffffff81178001 : s_show+0x2c1/0x330 [kernel] 0xffffffffa02240bc : 0xffffffffa02240bc [sisips] 0xffffffffa023b783 : 0xffffffffa023b783 [sisips] 0xffffffffa022abca : 0xffffffffa022abca [sisips] 0xffffffffa022d51a : 0xffffffffa022d51a [sisips] 0xffffffff81290745 : _atomic_dec_and_lock+0x55/0x80 [kernel] 0xffffffff81193611 : __fput+0x1a1/0x210 [kernel] 0xffffffff810e884e : __audit_syscall_exit+0x25e/0x290 [kernel] 0xffffffff8100b0d2 : system_call_fastpath+0x16/0x1b [kernel] ... |
可以看到,大量的size-4096分配来自内核模块”sisips”,有理由对它产生怀疑。(因为这是Symantec的内核模块,系统上没有它的debuginfo,所以systemtap解析不了它的backtrace符号,只能显示出16进制的地址)。为了验证该模块是否真的导致了内存泄露,可以暂时禁用它,观察/proc/slabinfo看size-4096是否停止疯涨,如果停了,显然该模块就有问题了。
另一种方法:kmemleak
检测内核内存泄漏还有另一种方法,就是利用kmemleak工具,它并不是针对某一个slab,而是针对所有的内核内存。详见:
用KMEMLEAK检测内核内存泄漏