怎样诊断slab泄露问题

Slab allocator是Linux kernel的内存分配机制,各内核子系统、模块、驱动程序都可以使用,但用完应该记得释放,忘记释放就会造成“内存泄露”(memory leak)。如果导致泄露的代码使用率很低倒也罢了,若是使用率很高的话,系统的内存会被迅速耗尽。

在以下案例中,132 GB 内存,仅剩21 GB空闲,还有16 GB的交换区被用掉了,显然内存使用相当紧张,而内存的主要去向是slab,slab用了89 GB,其中不可回收的部分(SUnreclaim)就占了88 GB:

再细看/proc/slabinfo,发现“size-4096”占用了84 GB之多(21720567*4096):

通常Slab的名字就表明了其用途,比如”inode_cache”、”dentry”什么的,根据发生泄露的slab cache的名字,大致就知道是哪个子系统或模块的问题,然而本例比较复杂,因为从”size-4096″的名称完全看不出slab cache的用途。

即便从名字知道了是哪个子系统的问题,为了进一步定位故障点,我们还要看到具体是哪些函数、哪些代码在分配内存才行。要如何诊断此类问题呢?

首先取决于kernel用的是slab还是slub,slab其实是一个统称,Linux kernel自2.6.23之后就已经从Slab进化成Slub了。

SLUB

如果是slub就比较容易,因为它提供了slub_debug参数,可以追踪slub分配内存的细节,既可以作为boot option打开,也可以通过对/sys/kernel/slab进行操作、在运行的系统上打开,具体方法参阅内核文档:Short users guide for SLUB 。

对RHEL来说,RHEL7才开始使用slub,之前的版本仍然是slab。判断kernel是否在使用slub有一个简单的方法,就是看/sys/kernel/slab目录是否存在,如果存在的话就是slub,否则就是slab。

SLAB

如果是slab的话,诊断就比较麻烦了,需要kernel编译的时候打开了”CONFIG_DEBUG_SLAB_LEAK”选项才行,默认是没打开的。对RHEL或CentOS来说,debug kernel打开了此编译选项,可以安装名为kernel-debug-*的rpm软件包,然后重启系统并选择此debug kernel即可,(参见https://access.redhat.com/solutions/358933 )。完成后/proc目录下会出现一个名为slab_allocators的文件,里面会记录类似如下的slab分配的信息,可以看到是什么代码在分配slab。缺点是只记录了直接调用的函数,没有完整的backtrace:

除了使用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,修改过的内容如下:

以root身份执行:

间隔一段时间再ctrl-c退出,看到结果如下:

大量的size-4096分配来自内核模块”sisips”,有理由对它表示怀疑。(因为这是Symantec的内核模块,系统上没有它的debuginfo,所以systemtap解析不了它的backtrace)。为了验证该模块是否真的导致了内存泄露,可以暂时禁用它,观察/proc/slabinfo看size-4096是否停止疯涨,如果停了,显然该模块就有问题了。