本文从7个方面进行说明:
1、 VMA到底是个什么鬼?
2、 Linux提供的VMA查看文件接口和命令接口说明。
3、 Page fault的产生原因分析以及与VMA的关系。
4、 物理内存、页表、进程之间的爱恨情仇。
5、 VSS、RSS、PSS、USS概念说明和实际的应用场景
6、 进程内存使用情况命令接口smem。
7、 内存泄漏的界定和监测办法。
VMA到底是个什么鬼?
VMA是Virtual MemoryAreas的缩写,虚拟内存区域,指的是用户空间0-3G范围内进程所拥有的多个全部零散分布的连续的虚拟内存空间。
注意上面这句话的三个定语:
用户空间0-3G范围内进程所拥有的:VMA区域存在于用户空间,当然“所拥有”并不是独占,也有可能是共享的。
多个全部零散分布的:进程拥有多个VMA区域,但是零散分布在0-3G空间。
连续的:这里说的连续,指的是单个VMA区域在虚拟地址空间是连续的。
看完上面的内容基本知道VMA是个什么东西了,那么VMA产生的原因是什么,为什么要搞个这个概念出来,VMA区域是客观存在的,你不定义VMA的struct,进程的代码段,数据段,堆,栈都是客观存在的,进程只要在代码段按序执行就好了,我管你叫什么名字,所以进程并不需要关心这个概念,真正需要VMA概念的是内核,通过VMA这个概念方便实现对所有进程内存空间的管理。
我们知道一个进程被fork出来,内核会维护一个task struct,这个结构的mmap成员维护了一个vm_area_struct的链表,这就是VMA的结构,内核通过维护这个结构来实现对进程的内存资源的管理、隔离和共享。
举个例子:进程使用malloc分配内存,这时C库没有足够的内存,内核的Lazy机制采用欺骗性手段,拖延分配内存的时机,这时,内存并没有被真正分配,内核只是把所有要分配的页表都映射到同一片已经清零的物理地址,并标记页表权限为readonly,但是当malloc返回的时候,对应的heap的VMA区域已经产生了,且已经进入内核管理的vm_area_struct链表中了,且权限被标记为读写权限,当我们向这片内存写入的时候,MMU在虚拟地址转换到物理地址的过程,发现页表的权限标记为readonly,而你要写入,这是不被允许的,于是产生Page fault,内核收到Page fault,查看对应的VMA链表,发现进程实际是有写的权限,于是分配页面,并改写页表,但是这整个过程,进程是不知道的,进程只是傻傻的以为,哈哈,老子又拿到了一片内存!
Linux提供的VMA查看文件接口和命令接口
Linux为用户提供了VMA查看的命令接口和文件接口。
命令接口:pmap
文件接口:/proc/pid/maps 、 /proc/pid/smaps
这些接口都可以看到进程的VMA区域分布情况,占用空间大小,和相应的权限
此处不做过多说明。
Pagefault的产生原因分析以及与VMA的关系
其实上面的“VMA到底是个什么鬼”章节,已经介绍了一种Pagefault产生的原因,也说明了与VMA的关系。下面列举产生Page fault的4中原因。
A. 动态分配内存,第一次写入,由于内核的Lazy机制,页表的权限为readonly,VMA权限为r+w,MMU产生Page fault,这种情况是真实的缺页,下面还会介绍并不是真实的缺页的情况。这种缺页也叫做Minor Page fault。
B. 进程访问自己的VMA区域以外的空间,这种行为被认为是非法的,同样会产生Page fault,但不会像A中一样引起真正的内存分配,反而会收到一个segv,程序被干掉。这种情况其实并不是真正的缺页。
C. 进程访问自己的VMA区域,但是并没有执行该操作的权限,比如进程尝试写代码段或者跳转到数据段执行,这也被认为是非法的,同样收到segv,程序被干掉。这种情况也不是真正的缺页。
D. 进程访问自己的VMA区域,且权限正确,但是对应的物理内存内容被swap到硬盘,这种情况毫无疑问才是彻彻底底的缺页,也会产生Page fault。这种缺页也叫做Major Page fault。
物理内存、页表、进程之间的爱恨情仇
此部分说起来比较复杂,直接上图:
上图中,process1044,1045,1054是进程的虚拟地址空间,绿色框图是他们各自的页表,图片最中间的是实际的物理内存。进程1044,1045,1054在虚拟地址空间各自拥有多个VMA区域,但是多个进程的VMA可能通过各自页表指向同一片内存区域,比如上图中libc代码段,三个进程的libc的VMA区域都通过页表指向同一片内存,就是说这三个进程共享这段内存。当然进程的VMA通过页表指向的内存也有可能是被这个进程独占的,比如上面三个进程的堆,都是各自独占的。
举个例子:假设上图中的内存是女神,女神为了衬托自己漂亮,身边难免要有几个丑女闺蜜,丑女闺蜜就是页表,屌丝process 1044,1045,1054对女神爱慕已久,只是苦于女神高高在上,无法接近,那么怎么办,曲线救国,先接近她的闺蜜,三个屌丝通过女神的三个闺蜜都轻松的获取到女神的基本爱好,喜欢吃什么,喜欢什么颜色,三个屌丝虽然都各自获取到一份信息,但是这个信息是客观存在的只有一份(这就是上面说的共享的情况),这时,其中一个屌丝1044,辛苦加班12个月,攒钱给闺蜜买了个大金链子,这个大金链子就是屌丝1044跟女神所拥有的独家美好记忆(这就是上面说的独占的情况)。
VSS、RSS、PSS、USS概念说明和实际的应用场景
话不多说,还是上图吧:
如图所示:
VSS是进程看到的自己在虚拟内存空间所占用的内存。
RSS是进程实际真正使用的内存。
PSS是多进程共享一片内存的容量取平均数,在加上自己独占的内存。
USS是进程所独占的内存容量。
通常,VSS≥RSS>PSS>USS,VSS之所以大于等于RSS,是考虑内核的Lazy机制并没有真正的分配内存以及内存被换出等情况。
好!继续举例子:女神怀了高富帅的娃,苦于腹中胎儿一天天长大,高富帅又不值得托付,所以决定在众多屌丝中择一名形象气质佳的男士作为配偶,屌丝1044,1045,1054踊跃报名,由于三人形象上都不分伯仲,所以女神的主要考察点改为:谁更在乎自己多一点。于是:
VSS:屌丝自以为对女神的在乎程度,知道女神的爱好,还给女神买大金链子都计算在内。
RSS:每个人的感知程度不一样,屌丝纵然万般宠爱,可女神没感觉到也是白搭,这个值是女神感受到的在乎程度。
PSS:请来裁判,考察屌丝日常的在乎程度,知道女神爱好+1分,送大金链子+3分,这个分数是比较客观的。
USS:单独考量每个屌丝送多少大金链子。
所以,综上,我们知道PSS值是比较客观的值,VSS是一个虚拟的值,RSS是一个实际的值,USS是独占的值。
进程内存使用情况命令接口smem
Linux提供命令接口smem来查看系统中进程的VSS、RSS、PSS、USS值。
还可以使用—pie选项和—bar选项进程图形化显示,更加一目了然。
内存泄漏的界定和监测办法
进程运行时申请的内存,在进程结束后会被全部释放。内存泄漏指的是运行的程序,随着时间的推移,占用的内存容量呈现线性增长,原因是程序中的申请和释放不成对。
如何监测程序是否出现了内存泄漏的情况,一方面我们可以通过上文提到的smem命令,连续的在多个时间点采样,记录USS的变化情况,如果这个值在连续的很长时间里呈现出持续增长,基本就可以断定程序存在内存泄漏的情况,然后你就可以手动去程序中查找泄漏位置。
另一方面,如果程序代码量较大,不方便查找定位内存泄漏点,可以使用valgrind和addresssanitizer来查找程序的内存泄漏。两种方式各有优劣,valgrind在虚拟机中运行程序,所以程序运行效率下降;addresssanitizer则需要改动源码,在源码中包含sanitizer/lsan_interface.h文件,然后在需要检查内存泄漏的地方调用函数__lsan_do_leak_check。两种方式都可以定位内存泄漏的位置。