linux通过meminfo 与 slab 定位内存泄漏

https://www.jianshu.com/p/a7af7c29c9e2

前言

问题真是一个接一个,做开发就是解决一个又一个问题吗?

像死机、内存泄漏这些问题很多时候是没有框架、设计或有了框架和设计但是团队没有统一遵循标准按着自己性子来导致的,统一的框架和设计也许会损失一定的灵活性,但是他会让你在编码的时候遵从一定的范式,且通过规范格式可以做到良好的自检查,例如将一个代码的实现分别放在A、B、C三个地方,A、B、C、分别干啥,接口是啥,A、B、C的范例等都在框架和设计中定义好了,每个编码人员按这个进行具体的开发编码工作,可以并行有序,甚至A、B、C进行不同的分工开发互不影响,比如A是解析配置的,B是做初始化的,C是业务处理主体。

对于应用开发,从框架、设计及编码质量这些工程角度避免低级错误,把开发的大部分时间用在算法、业务逻辑实现与优化等给客户带来良好体验与价值的地方,是一个软件团队需要努力的方向。

好吧,回到内存泄漏,这里指的是那些默默消耗了系统有限内存的操作,不仅仅指代码中申请而没有释放的内存,简单总结了下面几种情况进行检查和定位:

1,应用进程造成的内存泄漏

2,内核空间/或因应用进程的操作导致内核空间的内存泄漏

3,用户空间产生的临时文件(如日志等)造成的内存泄漏

应用进程造成的内存泄漏

1)采用ps或top命令进行观察

首先,我们习惯性的会看下那个进程在泄漏内存,我这里使用一个test_core_dump的测试进程,该进程每2秒钟会分配一个10000字节的空间,并作简单赋值(注意:如果仅malloc而不使用,编译器会优化,实际测试时将看不到内存泄漏的测试效果):

root@OpenWrt:/# ps

  PID USER      VSZ STAT COMMAND                                                     

14444 root    15984 S    test_core_dump

省略不必要的信息,这里,我们主要看VSZ这个字段,这个字段表示该进程的虚拟内存大小,单位为KBytes,也就是此时我的测试进程已经占用近16M的虚拟内存空间,那是否意味着物理内存就已经被吃掉16M呢,这个我等下再讲。

或者我们通过top命令,也可以观察进程的VSZ字段,这里我当时没抓速据,所以就不放速据了,不管是top还是ps,重点是看VSZ字段的变化,当你发现VSZ字段一直在变化,则很明显这个进程有内存泄漏的问题,比如我现使用ps再看:

root@OpenWrt:/# ps 

  PID USER      VSZ STAT COMMAND                                                     

14444 root 73884 S test_core_dump

我的测试进程已经占用近73M的虚拟地址空间。

2)查看/proc/meminfo

一般我们在发现有进程内存泄漏时,还会去查看一个文件,就是/proc/meminfo,看系统还有多少内存,比如我在上一步第一次ps时就去看了这个文件:

root@OpenWrt:/# cat /proc/meminfo

MemTotal:        125064 kB

MemFree:          28536 kB

Buffers:            8128 kB

Cached:            27844 kB

SwapCached:            0 kB

Active:            23004 kB

Inactive:          19996 kB

Active(anon):      10888 kB

Inactive(anon):      696 kB

Active(file):      12116 kB

Inactive(file):    19300 kB

Unevictable:          0 kB

Mlocked:              0 kB

SwapTotal:            0 kB

SwapFree:              0 kB

Dirty:                0 kB

Writeback:            0 kB

AnonPages:          7092 kB

Mapped:            4648 kB

Shmem:              4556 kB

Slab:              36684 kB

SReclaimable:      5792 kB

SUnreclaim:        30892 kB

KernelStack:        704 kB

PageTables:          588 kB

NFS_Unstable:          0 kB

Bounce:                0 kB

WritebackTmp:          0 kB

CommitLimit:      62532 kB

Committed_AS:      37800 kB

VmallocTotal:    1048372 kB

VmallocUsed:      12308 kB

VmallocChunk:    1020828 kB

看了系统还有MemFree:          28536 kB,约28M内存(我在系统启动完成时,就看了一次,大概是剩39M内存),那是不是这11M内存都是我的测试程序吃了呢?从第一次ps看到的虚拟内存16M来看,好像数据对不上?

不妨在第二次ps的时候,看下/etc/meminfo的信息:

root@OpenWrt:/# cat /proc/meminfo

MemTotal:        125064 kB

MemFree:          21936 kB

Buffers:            8128 kB

Cached:            27940 kB

SwapCached:            0 kB

Active:            28940 kB

Inactive:          19976 kB

Active(anon):      16808 kB

Inactive(anon):      692 kB

Active(file):      12132 kB

Inactive(file):    19284 kB

Unevictable:          0 kB

Mlocked:              0 kB

SwapTotal:            0 kB

SwapFree:              0 kB

Dirty:                0 kB

Writeback:            0 kB

AnonPages:        12900 kB

Mapped:            4716 kB

Shmem:              4652 kB

Slab:              37360 kB

SReclaimable:      6392 kB

SUnreclaim:        30968 kB

KernelStack:        648 kB

PageTables:          644 kB

NFS_Unstable:          0 kB

Bounce:                0 kB

WritebackTmp:          0 kB

CommitLimit:      62532 kB

Committed_AS:      94268 kB

VmallocTotal:    1048372 kB

VmallocUsed:      12308 kB

VmallocChunk:    1020828 kB

我们看到,第二次ps看到测试进程占用的虚拟内存为73M,而此时MemFree:          21936 kB,约为21M,也就是物理内存距上次的28M减少到21M,吃掉了7M,但虚拟内存是16M增长到73M,这明显不是一个换算关系,接下来我们看物理内存在哪里看:

3)查看物理内存占用,通过/proc/pid/stat或/proc/pid/statm(这里不能设置字体颜色,以粗体表示)

root@OpenWrt:/# cat /proc/14444/statm

21861 2332 202 1 0 20985 0

我们看到的21861表示VSZ,2332表示物理内存,这里的单位是页(page),我所用的系统当前页单位设置是4KB/页,所以可以得出:

VSZ = 21861 *4 = 87444KB,约87M(使用3)查看时,已距73M过了一段时间)

物理内存=2332 *4 = 9328,约9M,这时我们可以看到,基本可以和/proc/meminfo对的上了

或者,我们采用下面命令进行查看,同样可以看到该结果,这是下面文件参数太多,如果只是看内存,建议使用上个文件(注:下个文件中的89702400字段单位为Bytes而不是页,有些网上的文档解释成页,就误导了,而2332字段的单位是页)

root@OpenWrt:/# cat /proc/14444/stat

14444 (test_core_dump) S 496 14444 496 1089 29862 4194304 2488 0 0 0 36 14 0 0 20 0 1 0 7535179 89702400 2332 2147483647 4194304 4197032 2145364880 2145364392 1999211440 0 0 4096 0 2164588092 0 0 18 3 0 0 0 0 0 4262568 4262663 11354112 2145365870 2145365885 2145365885 2145365988 0

此时,通过上面的命令及文件的分析,基本可以定位或查找出应用进程的内存泄漏问题。

注:

1)ps和top命令很多网上的文档会说有VIRT、RES和SHR字段,但这可能是在PC端的linux,在嵌入式中,我遇到的仅有VSZ字段,相当于VIRT,所以要通过/proc/pid下的文件辅助

2)参考1):/proc/pid/stat字段说明(该文章用红色字体标明vsize的错误,赞)

3)参考2):进程的虚拟内存,物理内存,共享内存(包括statm文件格式说明),这里解释了虚拟内存和驻留内存(我理解实际使用的内存)的关系,就是上面VSZ和/proc/meminfo的数据关系

4)参考3):proc/meminfo 文件内存详解

内核/或因应用进程的操作导致内核空间的内存泄漏

排查内核空间的内存泄漏,这里主要争对slab来讲,尝试下面几步:

1)查看/proc/meminfo 中slab相关的字段:

root@OpenWrt:/# cat /proc/meminfo

MemTotal:        125064 kB

MemFree:            8536 kB

Buffers:            8128 kB

Cached:            28736 kB

SwapCached:            0 kB

Active:            20892 kB

Inactive:          21356 kB

Active(anon):      8628 kB

Inactive(anon):      692 kB

Active(file):      12264 kB

Inactive(file):    20664 kB

Unevictable:          0 kB

Mlocked:              0 kB

SwapTotal:            0 kB

SwapFree:              0 kB

Dirty:                0 kB

Writeback:            0 kB

AnonPages:          5336 kB

Mapped:            4664 kB

Shmem:              3936 kB

Slab:              57380 kB

SReclaimable:      23040 kB

SUnreclaim:        34340 kB

KernelStack:        640 kB

PageTables:          552 kB

NFS_Unstable:          0 kB

Bounce:                0 kB

WritebackTmp:          0 kB

CommitLimit:      62532 kB

Committed_AS:      23520 kB

VmallocTotal:    1048372 kB

VmallocUsed:      12308 kB

VmallocChunk:    1020828 kB

我们看到,MemFree:            8536 kB,约只剩8M内存,通过查看slab相关字段,Slab:              57380 kB我们看到约占用了57M空间,其中可回收的slab占,SReclaimable:      23040 kB,不可回收的slab占,SUnreclaim:        34340 kB,再看下半天前保留下来的meminfo(为了查找问题,提前保留的信息以作对比)

root@OpenWrt:/# cat /proc/meminfo

MemTotal: 125064 kB

MemFree:          21196 kB 

Buffers:            8128 kB 

Cached:            26456 kB 

SwapCached:            0 kB 

Active:            22968 kB 

Inactive:          21528 kB 

Active(anon):      10984 kB 

Inactive(anon):      588 kB 

Active(file):      11984 kB 

Inactive(file):    20940 kB 

Unevictable:          0 kB 

Mlocked:              0 kB 

SwapTotal:            0 kB 

SwapFree:              0 kB 

Dirty:                0 kB 

Writeback:            0 kB 

AnonPages:          9948 kB 

Mapped:            4680 kB 

Shmem:              1660 kB 

Slab:              42372 kB 

SReclaimable:      11800 kB 

SUnreclaim:        30572 kB 

KernelStack:        624 kB 

PageTables:          572 kB 

NFS_Unstable:          0 kB 

Bounce:                0 kB 

WritebackTmp:          0 kB 

CommitLimit:      62532 kB 

Committed_AS:      26704 kB 

VmallocTotal:    1048372 kB 

VmallocUsed:      12308 kB 

VmallocChunk:    1020828 kB 

可以看出,slab大约增加了15M内存的使用(57-42),而我们再仔细看,这其中SReclaimable约前后相差12M(23-11),这部分空间是可以回收的。既然这块内存变化较大,那我们重点看下slab的内存分配情况:

2)cat /proc/slabinfo, 这里信息太多,不全部放上来,我采集了两个速据做对比,这两个速据之间有一个动作,第3)步再讲:

linux通过meminfo 与 slab 定位内存泄漏 图1 linux通过meminfo 与 slab 定位内存泄漏 图2

有一个比较好的命令可以显示的比cat /proc/slabinfo更直观,但是我之前没有去了解,因为习惯性动作,直接cat文件,虽然一直觉得这个文件不是很直观,这个第4)步讲一下。我们来看这个数据,按照常规的逻辑,先找最大的那个数,或者我们惯性觉得最大的数就是异常的,在图1中我们看到dentry比较异常,共计153381个num_objs对象,每个对象136字节,占用空间约19M,我们看图2第二个异常kmalloc-128,共计124200个num_objs对象,每个对象128字节,占空间约15M。

通过分析内存占用较大的slab可以定位大概是哪里的问题,比如这两个占用内存比较大的slab,第一个dentry和文件操作相关,应该是应用层频繁的文件操作导致内核频繁申请dentry内存所致;第二个kmalloc-128是内核模块自己申请内存导致,这个可以首先排查自己私有模块用到128字节以内分配的调用,然后排查内核本身(如果这里有问题,内核本身的问题概率较少)。

3)尝试释放Slab占用的cache内存空间

虽然我们看到有两个slab占用较多,但是不能说这就是内存泄漏(或者也可以说是一种泄漏吧,只是不是因为kmalloc后没有kfree),也许是他就是要用到这么多内存,只是因为某种设计原因导致他过多和过快的占用了内存,影响到系统的稳定性。因为我们前面也看到,SReclaimable:      23040 kB有23M之多,这些是可回收和利用的,只是我们的系统还没有触发回收机制。

当前做一个简单的尝试,使用命令:

echo 2 > /proc/sys/vm/drop_caches

该命令释放dentries and inodes的可回收slab内存。

我们再来看,释放后的meminfo和slab:

root@OpenWrt:/# cat /proc/meminfo

MemTotal:        125064 kB

MemFree:          33912 kB

Buffers:            8128 kB

Cached:            27108 kB

SwapCached:            0 kB

Active:            20140 kB

Inactive:          20456 kB

Active(anon):      8616 kB

Inactive(anon):      692 kB

Active(file):      11524 kB

Inactive(file):    19764 kB

Unevictable:          0 kB

Mlocked:              0 kB

SwapTotal:            0 kB

SwapFree:              0 kB

Dirty:                0 kB

Writeback:            0 kB

AnonPages:          5416 kB

Mapped:            4664 kB

Shmem:              3948 kB

Slab:              33716 kB

SReclaimable:      2936 kB

SUnreclaim:        30780 kB

我们看到,MemFree:          33912 kB,从8M提升到了33M,slab从57M降低到33M,差不多回收了20多M内存,基本吻合,以此也可进一步判断这些内存不是因为没有kfree进了黑洞,而是因为kfree后还没有被系统回收和利用。那么我看在看看slabinfo,看数据是否也吻合,我们可以参考图1与图2的对比:drop_caches后,dentry,共计7714个num_objs对象,每个对象136字节,占用空间约1M;kmalloc-128,共计97350个num_objs对象,每个对象128字节,占空间约11M。对比drop_caches前,相当于(19+15-1-11)=22M,和回收了20多M内存也基本吻合。但从这里可以得出经验:

1,kmalloc-128还是有嫌疑,这个还是可以通过排除私有内核模块来处理,比如在系统启动后,所有模块加载完成的第一时间看slabinfo,如果在11M左右,说明确实需要,没有泄漏的异常,如果这个数据越来越大,通过drop_caches也回收不了且回收后大于11M较多,那就要检查内核相关模块;

2,dentry内存如果是因为应用层导致,则基本是可回收的,因为不是内核本身的异常,所以这时需要优化应用层相关的程序避免这个问题。

4)slabtop命令

Slab是用于存放内核数据结构缓存,再通过slabtop命令查看这部分内存的使用情况,这个和查看slabinfo效果一样,但是比较直观,具体格式这里不做解释,和slabinfo差不多:

root@OpenWrt:/# slabtop

Active / Total Objects (% used) : 217931 / 232100 (93.9%)

Active / Total Slabs (% used)      : 9664 / 9664 (100.0%)

Active / Total Caches (% used)    : 97 / 199 (48.7%)

Active / Total Size (% used)      : 37514.55K / 38973.26K (96.3%)

Minimum / Average / Maximum Object : 0.01K / 0.17K / 4096.00K

  OBJS ACTIVE  USE OBJ SIZE  SLABS OBJ/SLAB CACHE SIZE NAME                 

96900  91608  94%    0.13K  3230      30    12920K kmalloc-128

54259  54259 100%    0.13K  1871      29      7484K dentry

10962  7956  72%    0.02K    54      203      216K ft_dic_binary_cache

  8673  8660  99%    0.06K    147      59      588K buffer_head

  8475  8175  96%    0.01K    25      339      100K ft_head_cache

  5369  5215  97%    0.06K    91      59      364K sysfs_dir_cache

  5010  4974  99%    0.25K    334      15      1336K kmalloc-256

  2448  2409  98%    0.50K    306        8      1224K kmalloc-512

  2424  2421  99%    1.00K    606        4      2424K kmalloc-1024

  2175  2154  99%    0.02K    15      145        60K match_tree_feat_layer

  2085  1783  85%    0.25K    139      15      556K skbuff_head_cache

  1956  1918  98%    2.00K    978        2      3912K kmalloc-2048

  1880  1700  90%    0.09K    47      40      188K vm_area_struct

  1740  1677  96%    0.02K    12      145        48K ft_dic_tree_head_cache

  1608  1556  96%    0.32K    134      12      536K inode_cache

  1400  1400 100%    0.19K    70      20      280K filp

注:

1)参考一篇比较好的linux内存管理文章:详细讲解从用户空间申请内存到内核如何为其分配内存的过程,我把上面讲的内容放在一起,大概就是一个这样的图:

linux通过meminfo 与 slab 定位内存泄漏 图3

2)参考:Linux Malloc分析-从用户空间到内核空间

3)drop_caches相关参考,及设置内存回收的条件和时机:Linux服务器Cache占用过多内存导致系统内存不足问题的排查解决Linux服务器Cache占用过多内存导致系统内存不足问题的排查解决(续)

用户空间产生的临时文件(如日志等)造成的内存泄漏

这个其实开发人员自己心里可能是清楚的,因为这些临时文件基本是自己产生的,但因为缺乏统一设计(比如统一使用syslog)和没上心,可能把这个问题带到测试或客户环境,可以通过命令du -sh查看:

linux通过meminfo 与 slab 定位内存泄漏 图4
上一篇:【linux性能优化】理解内存的Buffer和Cache


下一篇:Linux中/proc/目录的学习