目录
内存概念
物理内存
虚拟内存
内存泄漏
定位方法和手段
1.MemInFo
MemTotal
MemFree
MemAvailable
Cached
2 vmalloc info
3.Kmemleak
算法原理
使用方法
参考文献与链接:
如果你点进这篇文章,那么要么你是一个C\C++程序员,要么你曾经或多或少被内存泄漏问题所困扰。嗯,作为一个Linux嵌入式程序员,我自然是两者兼具。
在工作中,总是会遇到内存泄漏的问题,对于那些记性不太好的程序员,本人在此强烈建议如果你有手动分配内存的话,请一一记录,否则很多时候你的差记性,可能会对他人造成莫大的困扰。建议大家都能严格要求自己吧。
好吧前面是一些牢骚呢,下面我们来正式地聊一聊,面对内存泄漏和内存碎片,我们到底有些什么手段去检测吧。当然,限于本人的水平,本文的所有方法,仅仅是本人在工作中遇到、学习到的,可能存在着很大的局限性。如果各位看官,有什么新的方法,或者比较特别的手段也能用来检测内存泄漏,希望也能一块分享一下,共同进步呢。
内存概念
要聊到内存泄漏与内存碎片问题, 那么我们就必须要先来讲一下内存这个概念。
内存分为:
-
物理内存
物理内存:很好理解,物理内存就是硬件所提供的的内存大小 -
虚拟内存
虚拟内存:每个进程在运行时使用到的,一般都是虚拟内存。在Linux系统下,系统一般允许进程获得4G的内存。0-1GB为内核空间,而1-4GB为用户态空间。
而我们在进程中所使用到的虚拟内存地址,看起来地址是连续的,但是实际上,相对于物理器件来说,我们所分配的虚拟内存地址都是通过一定的映射关系之后,将不连续的物理内存地址拼接起来,形成一段看起来连续的虚拟内存地址。
而IOMMU则是用来隔离物理内存与虚拟内存的,并且将他们进行关系映射的模块。
当然,这里面还涉及到了分页、页表、slab算法、buddy算法等一系列比较复杂的东西,但是由于不在本次的讨论范围内,那就还是下次再议吧。
总的来说,内存的概念可以比较简单地如上理解。
内存泄漏
那么,什么是内存泄漏呢?
简单地来说,就是当我们手动分配了内存(x_malloc等),却忘记手动释放内存(x_free),那么当这个程序被销毁或者退出时,这部分被手动分配的内存,并不会自动释放,返回给系统进行再次管理,而是会永久分配,但是却无人使用。当我们的程序不断地运行和退出时,就会造成无人使用的内存越来越多,从而导致内存被耗尽,出现各种各样的问题。
所以,再次提醒,一定要记得手动释放掉自己分配的内存。求求各位了。
那么有什么手段去分析,某个程序到底是否存在内存泄漏呢?
定位方法和手段
1.MemInFo
我觉得在我们使用不同的手段去检测前,我们一定要了解Linux下,系统内存的状况。
通过如下命令,能看到Linux当前的内存状态
cat /proc/meminfo
(负责输出/proc/meminfo的源代码是:fs/proc/meminfo.c : meminfo_proc_show())
MemTotal: 3809036 kB
MemFree: 282012 kB
MemAvailable: 865620 kB
Buffers: 0 kB
Cached: 854972 kB
SwapCached: 130900 kB
Active: 1308168 kB
Inactive: 1758160 kB
Active(anon): 1010416 kB
Inactive(anon): 1370480 kB
Active(file): 297752 kB
Inactive(file): 387680 kB
Unevictable: 0 kB
Mlocked: 0 kB
SwapTotal: 4063228 kB
SwapFree: 3357108 kB
Dirty: 0 kB
Writeback: 0 kB
AnonPages: 2104412 kB
Mapped: 40988 kB
Shmem: 169540 kB
Slab: 225420 kB
SReclaimable: 134220 kB
SUnreclaim: 91200 kB
KernelStack: 5936 kB
PageTables: 35628 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 5967744 kB
Committed_AS: 5626436 kB
VmallocTotal: 34359738367 kB
VmallocUsed: 351900 kB
VmallocChunk: 34359363652 kB
HardwareCorrupted: 0 kB
AnonHugePages: 139264 kB
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
DirectMap4k: 204484 kB
DirectMap2M: 3915776 kB
然后我们可以看到如上输出。
当然输出信息有很多,但是我们只需要关注部分比较重要的即可。
MemTotal
系统从加电开始到引导完成,firmware/BIOS要保留一些内存,kernel本身要占用一些内存,最后剩下可供kernel支配的内存就是MemTotal。这个值在系统运行期间一般是固定不变的。可参阅解读DMESG中的内存初始化信息。
MemFree
表示系统尚未使用的内存。[MemTotal-MemFree]就是已被用掉的内存。
MemAvailable
有些应用程序会根据系统的可用内存大小自动调整内存申请的多少,所以需要一个记录当前可用内存数量的统计值,MemFree并不适用,因为MemFree不能代表全部可用的内存,系统中有些内存虽然已被使用但是可以回收的,比如cache/buffer、slab都有一部分可以回收,所以这部分可回收的内存加上MemFree才是系统可用的内存,即MemAvailable。/proc/meminfo中的MemAvailable是内核使用特定的算法估算出来的,要注意这是一个估计值,并不精确。
Cached
程序运行的缓存,这部分内存属于是释放后,不会立即返还给系统。而是会作为cached内存,当程序再次访问内存时,会先查找cached内存(分L1 L2 L3),如果无法cache hint,就会出发缺页错误,从而进行新的内存访问。
echo 3 > /proc/sys/vm/drop_caches
调用上面的命令,即可将所有的cached内存返还给MemFree上来。
当然,在我们考虑有多少cache可供回收的时候,首先要知道的是:不同版本的”free”命令计算cache值的算法不同,据不完全统计举例如下:
1、版本:procps-3.2.8-36
cache值等于/proc/meminfo中的”Cached”;
2、版本:procps-3.3.9-10.1
cache值等于/proc/meminfo的 [Cached + SReclaimable];
3、版本:procps-ng-3.3.10-3
cache值等于/proc/meminfo的 [Cached + Slab]。
所以,到这里,我们就有一个“古法”的手段去检测是否出现了内存泄漏。
也就是我们可以不断运行我们的程序,然后再去监测/proc/meminfo里的相关信息,如果当我们drop cache后,仍然存在着Memfree不断减少的情况,那么基本上可以确定是存在内存泄漏了。
2 vmalloc info
在/proc/meminfo中,我们最多只能大概确定,当前的程序运行是否存在内存泄漏的情况。但是当我们实际在开发的时候,由于涉及到很多模块。我们实际上并不能确定,内存泄漏到底是因为哪个模块引起的。
这个时候,我们有更加方便的proc信息可以查看。即
cat /proc/vmallocinfo
通过vmalloc分配的内存都统计在/proc/meminfo的 VmallocUsed 值中,但是要注意这个值不止包括了分配的物理内存,还统计了VM_IOREMAP、VM_MAP等操作的值,譬如VM_IOREMAP是把IO地址映射到内核空间、并未消耗物理内存,所以我们要把它们排除在外。从物理内存分配的角度,我们只关心VM_ALLOC操作,这可以从/proc/vmallocinfo中的vmalloc记录看到:
注:/proc/vmallocinfo中能看到vmalloc来自哪个调用者(caller),那是vmalloc()记录下来的,相应的源代码可见:mm/vmalloc.c: vmalloc > __vmalloc_node_flags > __vmalloc_node > __vmalloc_node_range > __get_vm_area_node > setup_vmalloc_vm
通过运行和销毁程序,我们就可以看到对应的内存的分配和释放情况。正常来说,程序运行时,我们会看到vmalloc记录了所有调用vmalloc的调用栈及其分配到的虚拟内存地址。
而当销毁时,这些内存都会被释放掉,而消失在proc中。
如果当我们运行并退出程序后,vmallocinfo中还存在着我们程序中的函数分配信息,那么基本上可以确认,就是这个函数所分配的内存没有释放。
3.Kmemleak
此外,也有很直接的工具可以进行内存泄漏的检测。
分别是kmemleak、valgrind\perf等
由于在我工作的环境中,没法用上valgrind、perf等工具,所以接触不深,加之网上已经有很多这些工具的博客,就不班门弄斧了。
主要说一下kmemleak吧。
Kmemleak主要是Linux提供的工具,可以通过内核编译选项进行打开。
但是其检测到的信息,可能并不是特别准确,他独特的检测算法,只能展示出有可能的内存泄漏,但也不一定就是。
算法原理
Kmemleak算法的原理简单地来说,就是将通过kmalloc\vmalloc\kmem_cache_alloc等一系列分配函数将其指针连同分配大小、堆栈跟踪等信息一块存储在红黑树中。并且会追踪对应的release函数,检测内存释放,并将其从红黑树中删除。
扫描算法步骤:
- 将所有对象标记为白色(剩余的白色对象稍后将被视为孤立对象)
- 从数据部分和堆栈开始扫描内存,根据存储在 rbtree 中的地址检查值。如果找到指向白色对象的指针,则将该对象添加到灰名单
- 扫描灰色对象匹配地址(一些白色对象可以变成灰色并添加到灰色列表的末尾)直到灰色集合完成
- 剩余的白色对象被认为是孤立的,并通过 /sys/kernel/debug/kmemleak 报告
一些分配的内存块具有存储在内核内部数据结构中的指针,它们不能被检测为孤儿。为了避免这种情况,kmemleak 还可以存储指向需要找到的块地址范围内地址的值的数量,以便该块不被视为泄漏。一个例子是 __vmalloc()。
使用方法
To display the details of all the possible scanned memory leaks::
cat /sys/kernel/debug/kmemleak
To trigger an intermediate memory scan::
echo scan > /sys/kernel/debug/kmemleak
To clear the list of all current possible memory leaks::
echo clear > /sys/kernel/debug/kmemleak
泄漏堆栈信息:
当我们看到如上的堆栈信息后,即可知道我们的程序在那些地方会出现有可能的内存泄漏,然后我们感觉调用堆栈信息去查找相关代码,根据代码流程分析是否存在内存泄漏即可。
这个博客到目前就总结了以上内存泄漏的定位方法和一些概念性的东西,希望能帮到各位苦苦挣扎的C\C++程序员。也希望能抛砖引玉,有人分享更好的定位方法。
参考文献与链接:
Kernel Memory Leak Detector — The Linux Kernel documentation
用kmemleak检测内核内存泄漏 | Linux Performance
https://www.cnblogs.com/klcf0220/p/5502254.html
Kernel Memory Leak Detector — The Linux Kernel documentation
Kmemleak--Kernel space内存泄露分析工具 - SigmaStarDocs