Linux内存管理之四大法器

吴凤雏
原创 1488       2019-04-08  

 

神通广大的河妖,能呼风唤雨,通天入地,法力无边。时不时要出来作怪,为害人间。Linux内存管理就是这么个狠角色。程序运行正常时,母慈子孝,提供稳定的服务,一旦出现个越界写或者错误释放内存,轻则运行结果出错,大多数时候直接宕机了。危害之巨大可不止鸡飞狗跳。既然内存问题是悬在每个开发者头上的达摩克利斯之剑,那么了解几个大法器与之对抗就尤为重要了!为了内存问题这个河妖作怪的时候能被镇住,就用有限的篇幅介绍河妖的妖法如何被法器降服。

 

知己知彼

他强任他强,开发过程中和排查问题时,对原理的了解能够指导我们的程序设计。排查问题时,快速缩小问题范围。从基本情况开始。

通过top命令,可以获取到一些重要的内存使用情况参数。如下图所示:

今天我们关注如下的几个指标:

Linux内存管理依赖的是brk,pmap,punmap这些系统调用,每个进程有独立的虚拟地址空间,进程分配的是虚拟地址空间,当进程访问已分配的内存时,检测虚拟地址对应的物理地址是否在物理内存中,如果不在则产生缺页中断,才真正分配物理内存。如果物理内存耗尽则根据内存替换算法将部分内存写入物理磁盘中。所以程序中分配的内存一定会反应在VIRT中,但是不一定会反应在RES中。通常看到VIRT会比RES大很多。那么VIRT或者RES的异常增长都是可疑的内存泄漏。

brk申请的是堆上的内存,堆是向上生长的,堆顶的位置表示占用堆的大小,也就是地址空间的大小。如下图,释放lp3,直接调整堆顶指针,释放lp1时,lp1并调整堆顶指针,需要等lp2释放以后才会释放。所以堆上申请的空间后申请的释放了,之前申请的才可能释放。

 

pmap用于申请映射区的内存,可以将文件直接映射到内存,从而直接在内存中操作文件。也可以不映射文件,创建一个匿名内存映射,malloc就用这种方式申请大于M_MMAP_THRESHOLD长度的内存。

Linux家大宅深,向系统直接申请内存由他直接提供开销比较大,毕竟是隐藏在幕后的老大,所以通常程序使用内存是通过glibc这个管家。而malloc,free这些接口就是我们与glibc管家交互的接口。glibc有选择性的调用brk,mmap系统调用,并且合理控制一次申请空间的大小来减少系统调用,提高运行效率。glic管理内存有一个重要的参数M_MMAP_THRESHOLD,这个值默认是128KB,在32位系统中最大值是512KB,在64位系统中,最大值是4MB,在运行过程中会动态变化,比如申请了小于最大值的空间又释放了,那么会调整成释放的大小的页对齐的大小。

程序调用malloc时,基本的原则是对于大于M_MMAP_THRESHOLD的申请,使用mmap在映射区申请匿名内存映射。小于M_MMAP_THRESHOLD的申请调用brk。可以把malloc的实现当做实现了一个记录池,内部算法决定内存池的扩展和收缩。扩展和收缩可能不是以我们在程序中申请或释放的大小进行的。而是以一定的步长进行。比如对于堆的扩展和收缩可能是如下情形。

以M_MMAP_THRESHOLD为步长扩展的情形。

以M_MMAP_THRESHOLD为步长收缩的情形。

同理,对于pmap映射区内存的申请也是以一定的步长申请和释放。

了解glibc的这些操作,以便在监控系统调用的时候,理解程序中申请的内存并不一定会直接反映在系统调用中。比如在程序中写的是malloc(10);在系统调用中并不会出现brk分配出10字节的情形。在Redhat 64位系统中测试的情形,

程序中执行

char *lpBuf = (char *)malloc(20);

free(lpBuf);

得到如下系统调用:实际堆被上推了0x21000的字节空间。

调用free(lpBuf);后,并没有产生系统调用调整堆顶指针。

程序中执行

char *lpBuf = (char *)malloc(1 * 1024 * 1024);

free(lpBuf);

得到如下系统调用:

这里可以反映实际的申请和释放。当然这并不是普遍的情形,在多线程场景下有更复杂的算法和策略。

有些基本的了解,针对这个河妖最大的妖法——内存泄漏,我们需要找到制服的法宝。内存泄漏很容易发现,但是找到哪里作妖却不是那么容易。好在有一系列的工具可以帮助我们找到蛛丝马迹。接下来就介绍一下,哪些法器可以拿出来显显身手。

 

法器之代码静态分析工具

静态检查是规避内存泄漏的重要措施,可以做到早发现,早解决,花费时间成本最小。静态分析工具可以检查编码规范,安全类,致命类,逻辑类问题等。静态分析工具通过词法分析、语法分析、控制流和数据流分析对程序进行扫描,根据设定的规则发现问题。每次提交代码都应该确保通过静态分析检查。实践表明,静态分析能够检查出软件开发周期中一半的错误,提早发现并修复能极大提高代码质量。对于c++语言程序来说,常用的静态分析工具有cppcheck, pc-lint, Coverity等。这些分析工具各有优劣,可以考虑项目组成员对不同工具的熟悉程度以及项目来决定使用哪种分析工具。运用时的优缺点如下,

优点:全是优点

缺点:1.通常会报大量无效的警告,配出这些无效警告也相当耗费时间。

2.有些分析工具不是免费的。

 

法器之封装内存管理接口增加监测信息

大型系统都会提供完整的内存监测手段,包括统计分配和释放信息,当前正在使用的内存块状态等信息。可以通过命令行或者其他可见的方式查询内存使用状态。比如对于内存池的监控、通信中消息节点的使用情况等。便捷的查看手段可以在尽早的阶段发现内存的异常使用情况。甚至可以在已分配内存中添加执行代码行号,函数名等信息,并把已分配地址加入信息记录的结构或者类中。但是任何额外的信息都是需要系统开销的,所以需要评估这些开销是否影响到系统达到理想的性能。运用时的优缺点如下,

优点:能迅速发现内存泄漏并迅速定位、无需额外的工具定位

缺点:影响系统性能

 

法器之运行时监测

运行时监测工具主要有valgrind memcheck, Rational purity等。功能异常强大,能检查出未初始化的内存、读写越界、内存覆盖、内存动态管理错误、申请和释放不一致、申请和释放不匹配、释放后仍然写、内存泄漏等几乎所有内存问题。

优点:功能强大,几乎能定位所有的潜在内存问题

缺点:可能需要安装,对于有些在特定环境才能复现的问题,又因为限制没办法安装新的程序就无能为力了。为了更精确的定位需要程序编译时加-g参数

 

法器之strace工具

再厉害的检查工具都可能有漏网之鱼,在极端情况下,还是需要进一步获取信息进行分析。好在还是有一些功能能够给我们打辅助。Strace就是类似终极武器的工具。它是Linux系统下的跟踪系统调用的工具。基于pstrace实现,可以跟踪程序执行过程中的所有系统调用。Strace配置灵活,功能强大,可以用来统计指定的系统调用的次数以及花费的时间,还能统计成功和失败的次数等。对于调查系统的瓶颈有非常大的作用。对于调查内存问题,跟踪指定的brk、pmap、punmap等系统调用就能分析内存使用情况。新版本的strace支持输出系统调用前的堆栈信息。堆栈信息是解开一切问题的钥匙,拿到钥匙问题就迎刃而解。

优点:使用灵活一般Linux系统自带、可以随时attach上指定的进程,对黑盒程序也可以分析、编译成可执行程序只有大概2M多拷贝方便。

缺点:并不能直观反映程序调用。不能直接报错,需要通过分析得出结论、输出堆栈的版本目前需要自行编译。

 

以上工具合理运用,能够排查所有内存相关问题。再厉害的妖也能镇压。实践出真知,工具的应用不是毫无门槛,通常也需要多种工具配合使用才能最终找到问题所在,只有多多使用才能融会贯通。

恒生技术之眼原创文章,未经授权禁止转载。详情见转载须知

联系我们

恒 生 技 术 之 眼