SLUB DEBUG分析内存泄漏


一、打开内核SLUB DEBUG选项

slub内核宏

二、观察slabinfo,找异常内存对象

输出slab信息,观察一段时间。发现kmalloc-256模块的active和num数量只增不减(正常维持稳定)。

cat /proc/slabinfo

kmalloc-256
每一列所代表的含义为:

name:
	slab object对象的名称
active_objs:
	活动对象(slab object)个数,即被申请走的对象个数(正在被使用)。
num_objs:
	总对象(slab object)个数,slab本身具备缓存作用,所以num_objs可能会比active_objs要大,这是一种合理的现象。
objsize:
	每个对象(slab object)大小,以字节为单位。
objperslab:
	slab中存放的是对象(slab object),这个指标表示一个slab中包含多少个对象(slab object)。
pagesperslab : 
	tunables:一个slab占用几个page内存页。可以算一下:
	一个slab的大小为320*12=3840,小于4096(我这里,内存页为4K),所以一个slab只需占用1个page内存页。另外,cache对象本身需要存储一个额外的管理信息,即有overhead,所以一个xfs_buf slab的大小不止3840。
active_slabs:
	活动slab个数。
num_slabs:
	总slab个数。

三、打开并分析slab trace,找异常的函数栈

从slabinfo中得知,kmalloc-256存在异常,因此单独分析kmalloc-256对象的日志。
1)trace日志是将调用alloc/free的栈都打出来,日志很多,可以通过以下方式形成脚本在目标机器上运行,收集trace日志:kmalloc-256_trace.log。

echo 1 > /sys/kernel/slab/kmalloc-256/trace 
sleep 60 
echo 0 > /sys/kernel/slab/kmalloc-256/trace

2)输出的trace日志存在相互嵌套,很难看,通过以下方式分别提炼alloc和free的trace日志

grep "TRACE kmalloc-256 alloc" kmalloc-256_trace.log | awk -F] '{print $3}' | sort > kmalloc-256-alloc.txt
grep "TRACE kmalloc-256 free" kmalloc-256_trace.log | awk -F] '{print $3}' | sort > kmalloc-256-free.txt

3)对比alloc和free日志,针对空缺部分在完整的日志文件kmalloc-256_trace.log中找到调用栈,并分析相关代码是否存在内存泄漏。
alloc&free

四、分析alloc_calls/free_calls,找异常模块/函数

1、alloc_calls的输出:

/sys/kernel/slab #  cat /sys/kernel/slab/kmalloc-256/alloc_calls    
    360 os_alloc_mem+0x1c/0x38 [mt_wifi] age=18174177/18175141/18176898 pid=870-1806 cpus=0-1
      2 warp_os_alloc_mem+0x1c/0x38 [mtk_warp] age=18175182/18176017/18176853 pid=885-1806 cpus=0-1
      1 pie_dev_event_handler+0x1f48/0x2a88 [private_ie] age=18173110 pid=1712 cpus=1
  19672 osl_malloc_t+0x14/0x20 [fastnat] age=3352/10021958/18166404 pid=0-32581 cpus=0-1
      2 0xffffffc009961a84 age=18175476/18175477/18175478 pid=1022 cpus=0
     20 __devm_request_region+0x40/0xd0 age=18177982/18178127/18178200 pid=1 cpus=0-1
     ......

2、free_calls的输出:

/sys/kernel/slab #  cat /sys/kernel/slab/kmalloc-256/free_calls  
   7740 <not-available> age=4313127012 pid=0 cpus=0
     97 km_lib_netlink_broadcast_handle_msg+0xc0/0x110 [kmlib] age=65361/5917889/15022301 pid=0-26711 cpus=0-1
  12908 os_free_mem+0x30/0x40 [mt_wifi] age=72/8951380/18231338 pid=0-32533 cpus=0-1
    367 osl_mfree_t+0xc/0x9e8 [fastnat] age=18266/8820430/17975835 pid=0-2771 cpus=0-1
     10 worker_thread+0x310/0x4f8 age=280676/6416483/14530660 pid=671-31707 cpus=0-1
     ......

3、对比alloc和free:alloc和free调用次数和变化趋势均不匹配,怀疑存在问题。

18674 osl_malloc_t+0x14/0x20 [fastnat] age=2449/9931130/17556324 pid=0-32581 cpus=0-1
  342 osl_mfree_t+0xc/0x9e8 [fastnat] age=33006/8833405/17365749 pid=0-2771 cpus=0-1
18866 osl_malloc_t+0x14/0x20 [fastnat] age=7370/9906247/17632755 pid=0-32581 cpus=0-1
  345 osl_mfree_t+0xc/0x9e8 [fastnat] age=9582/8832468/17442089 pid=0-2771 cpus=0-1
18927 osl_malloc_t+0x14/0x20 [fastnat] age=694/9950561/17709119 pid=0-32581 cpus=0-1
  350 osl_mfree_t+0xc/0x9e8 [fastnat] age=1964/8782113/17518419 pid=0-2771 cpus=0-1
19016 osl_malloc_t+0x14/0x20 [fastnat] age=6878/9980086/17785345 pid=0-32581 cpus=0-1
  352 osl_mfree_t+0xc/0x9e8 [fastnat] age=3397/8807940/17594558 pid=0-2771 cpus=0-1
19040 osl_malloc_t+0x14/0x20 [fastnat] age=1962/10043607/17861487 pid=0-32581 cpus=0-1
  352 osl_mfree_t+0xc/0x9e8 [fastnat] age=79564/8884108/17670726 pid=0-2771 cpus=0-1
19181 osl_malloc_t+0x14/0x20 [fastnat] age=303/10045633/17937593 pid=0-32581 cpus=0-1
  357 osl_mfree_t+0xc/0x9e8 [fastnat] age=19440/8835147/17746868 pid=0-2771 cpus=0-1
19489 osl_malloc_t+0x14/0x20 [fastnat] age=3707/9962833/18013829 pid=0-32581 cpus=0-1
  361 osl_mfree_t+0xc/0x9e8 [fastnat] age=14114/8813053/17823100 pid=0-2771 cpus=0-1
19557 osl_malloc_t+0x14/0x20 [fastnat] age=367/10004402/18090135 pid=0-32581 cpus=0-1
  363 osl_mfree_t+0xc/0x9e8 [fastnat] age=20304/8840649/17899447 pid=0-2771 cpus=0-1
19672 osl_malloc_t+0x14/0x20 [fastnat] age=3352/10021958/18166404 pid=0-32581 cpus=0-1
  367 osl_mfree_t+0xc/0x9e8 [fastnat] age=18266/8820430/17975835 pid=0-2771 cpus=0-1
  367 osl_mfree_t+0xc/0x9e8 [fastnat] age=18989/8821153/17976558 pid=0-2771 cpus=0-1
19709 osl_malloc_t+0x14/0x20 [fastnat] age=9333/10079328/18242660 pid=0-32581 cpus=0-1

4、分析到此处,大致可以定位到具体异常的功能模块(案例中为fastnat),此时卸载fastnat模块,发现kmalloc-265迅速下跌,接下来移植保持稳定,再次证明fastnat存在问题。
卸载fastnat

五、深入异常模块/函数,找异常代码

异常范围缩小之后,此时就需要结合该异常模块的内存申请和释放情况分析,本案例中fastnat内存异常就是因为,hash表节点没有正常的逻辑释放,导致hash表节点只增不减,不断的消耗内存。
1、重点分析链表、队列、hash表等数据结构,该类内存的特点是申请和释放一般不在同一处(同一个函数中)。一般是先申请,加入数据结构,然后根据某种机制释放。因此需要排查释放机制是否生效,或者释放的代码是否执行,此时可在申请和释放位置增加日志来观察申请释放情况。
2、由于slab信息中显示的是kmalloc-256模块的内存异常,重点关注代码中对128-256大小内存块的申请和释放。
3、类似fastnat中的异常,其实不算纯粹意义上的内存泄漏,因为其并没有丢失内存索引,每个内存块依然保存在hash表中,当模块退出归还资源时,依然可以将内存归还给系统。根据这个特点,对于丢失内存索引的泄漏,可以通过卸载模块的方式,以单个数据结构为单位,分别对比其申请内存的大小/次数/节点数量和归还给系统的情况来排查是否存在异常。

补充

Kernel Memory Leak Detector:link
Linux内核的Kmemleak工具检查内存泄露链接:link


总结

内存泄漏问题一般不能一蹴而就,首先是要在偌大的系统中,缩小发生异常的范围。当可以基于某个小范围着手定位具体的异常位置后,需要想方设法结合模块的功能业务,代码逻辑制定排查方法,一点点寻找。

猜你喜欢

转载自blog.csdn.net/ID2442512720/article/details/131240554