引 言: top命令做为Linux下最经常使用的性能分析工具之一,能够监控、收集进程的CPU、IO、内存使用状况。好比咱们能够经过top命令得到一个进程使用了多少虚拟内存(VIRT)、物理内存(RES)、共享内存(SHR)。windows
最近遇到一个咨询问题,某产品作性能分析须要获取进程占用物理内存的实际大小(不包括和其余进程共享的部分),看似很简单的问题,但通过研究分析后,发现背后有不少故事……数据结构
1 VIRT RES SHR的准确含义

三个内存指标,VRIT,RES,SHR准确含义是什么?谁能告诉咱们?MAN页?Linux专家?SUSE工程师?Linus?谁能说出最正确答案?没人!由于唯有源代码才是最正确的答案。函数
那咱们就去看下源码吧,这就是开源软件的最大的好处。工具
首先这三个数据的源头,确定是内核,进程的相关数据结构确定是由内核维护。那么top做为一个用户空间的程序,要想获取内核空间的数据,就须要经过系统接口(API)获取。而proc文件系统是Linux内核空间和用户空间交换数据的一个途径,并且是很是重要的一种途径,这点和windows更倾向于基于函数调用的形式不一样。post
当你调用系统函数read读取一个普通文件时,内核执行对应文件系统的代码从磁盘传送文件内容给你。性能
当你调用系统函数read读取一个 proc文件时,内核执行对应的proc文件系统的代码从内核的数据结构中传送相关内容给你。proc文件和磁盘没有关系。只是系统接口而已。spa
而一个进程的相关信息,Linux所有经过/proc/<pid>/内的文件告诉了咱们。blog
以下,你可使用普通的文件读写工具,好比cat获取进程的各类信息。这比函数调用的方式灵活多了、丰富多了。接口

回到咱们的问题,top命令显示的进程信息,确定也是经过proc获取的,由于除此以外没有其余途径,没有系统函数能够作这个事情,top也不可能越过用户层直取内核获取数据。进程
带着以上信息,很快就能够从top的源码中找到关键代码:

啊哈,statm文件:

根据sscanf的顺序,第一个值是VIRT,第二个值是RES,第三个值是SHR!
等等,好像数值对不上,top显示的SHR是344k,而statm给出的是86!
再来看一行关键代码:

statm显示的是页数,top显示的是KB。X86下,一页是4KB,86 * 4 = 344。这就对了!
因而乎,咱们找到了最关键的入口,接下来按图索骥,看看内核是怎么产生statm文件内容就能够了。~~

proc_pid_statm函数负责产生statm文件内容,当你使用cat命令打印statm文件时,内核中的这个函数会执行。
proc_pid_statm获取进程的mm_struct数据结构,而这个数据结构就是进程的内存描述符,经过它能够获取进程内存使用、映射的所有信息。
进一步考察task_statm函数,能够看到:

第一个值(VIRT)就是mm->total_vm,即进程虚存的总大小,这个比较清晰,只要进程申请了内存,不管是malloc仍是堆栈仍是全局,都会计入这个值;
第二个值(RES)是mm->file_rss+mm->anon_rss;
第三个值(SHR)是mm->file_rss。
RES要和SHR结合者看,内核把物理内存分为了两部分,一部分是映射至文件的,一部分是没有映射至文件的即匿名内存,彻底和共不共享没有关系!
但file_rss为何叫作shared呢?应该是一种指示性表述,表示这部份内存多是共享的。但并不表明真正共享了。那么到底哪些计入file_rss?经过查阅相关代码,发现(可能有遗漏):
l 程序的代码段。
l 动态库的代码段。
l 经过mmap作的文件映射。
l 经过mmap作的匿名映射,但指明了MAP_SHARED属性。
l 经过shmget申请的共享内存。
即进程经过以上方式占用的物理内存,计入file_rss,也就是top的SHR字段。咱们看到通常这些内存都是以共享方式存在。但若是某个动态库只一个进程在使用,它的代码段就没有被共享着。
反过来再来看anon_rss统计的内容,是否就必定是独占的?也不是,好比新fork以后的子进程,因为copy on write机制,在页面被修改以前,和父进程共享。这部分值并不体如今top命令的SHR字段内。
综上所述top命令显示的SHR字段,并非准确描述了进程与其余进程共享使用的内存数量,是存在偏差的。
那么如何获取进程准确的共享内存数量?
2 获取进程准确的共享内存数量
咱们注意到在描述进程信息的proc/<pid>内,有一个smaps文件,里面展现了全部内存段的信息,其中有Shared_Clean Shared_Dirty Private_Clean Private_Dirty:几个字段。

找到相关代码,能够看到,一个页面若是映射数>=2计入Shared_* ; 若是=1计入Private_*。(脏页计入*_Dirty,不然计入*_Clean)

统计smaps文件内全部段的Shared_*值的总和就是进程准确的共享内存数量!
统计smaps文件内全部段的Private_*值的总和就是进程准确的独占内存数量!
3 总结
经过以上分析,咱们能够获得以下结论:
l top命令经过解析/proc/<pid>/statm统计VIRT和RES和SHR字段值。
l VIRT是申请的虚拟内存总量。
l RES是进程使用的物理内存总和。
l SHR是RES中”映射至文件”的物理内存总和。包括:
程序的代码段。
动态库的代码段。
经过mmap作的文件映射。
经过mmap作的匿名映射,但指明了MAP_SHARED属性。
经过shmget申请的共享内存。
l /proc/<pid>/smaps内Shared_*统计的是RES中映射数量>=2的物理内存。
l /proc/<pid>/smaps内Private_*统计的是RES中映射数量=1的物理内存。