Post

Linux下查看某一进程占用的内存

概述

想必在linux上写过程序的同学都有分析进程占用多少内存的经历,或者被问到这样的问题——你的程序在运行时占用了多少内存(物理内存)?通常我们可以通过top命令查看进程占用了多少内存。这里我们可以看到VIRT、RES和SHR三个重要的指标,他们分别代表什么意思呢?这是本文需要跟大家一起探讨的问题。当然如果更加深入一点,你可能会问进程所占用的那些物理内存都用在了哪些地方?这时候top命令可能不能给到你你所想要的答案了,不过我们可以分析proc文件系统提供的smaps文件,这个文件详尽地列出了当前进程所占用物理内存的使用情况。

关于内存的两个概念

要理解top命令关于内存使用情况的输出,我们必须首先搞清楚虚拟内存(Virtual Memory)和驻留内存(Resident Memory)两个概念。

虚拟内存 Virtual Memory

首先需要强调的是虚拟内存不同于物理内存,虽然两者都包含内存字眼但是它们属于两个不同层面的概念。进程占用虚拟内存空间大并非意味着程序的物理内存也一定占用很大。虚拟内存是操作系统内核为了对进程地址空间进行管理(process address space management)而精心设计的一个逻辑意义上的内存空间概念。我们程序中的指针其实都是这个虚拟内存空间中的地址。比如我们在写完一段C++程序之后都需要采用g++进行编译,这时候编译器采用的地址其实就是虚拟内存空间的地址。因为这时候程序还没有运行,何谈物理内存空间地址?凡是程序运行过程中可能需要用到的指令或者数据都必须在虚拟内存空间中。既然说虚拟内存是一个逻辑意义上(假象的)的内存空间,为了能够让程序在物理机器上运行,那么必须有一套机制可以让这些假象的虚拟内存空间映射到物理内存空间(实实在在的RAM内存条上的空间)。这其实就是操作系统中页映射表(page table)所做的事情了。内核会为系统中每一个进程维护一份相互独立的页映射表。。页映射表的基本原理是将程序运行过程中需要访问的一段虚拟内存空间通过页映射表映射到一段物理内存空间上,这样CPU访问对应虚拟内存地址的时候就可以通过这种查找页映射表的机制访问物理内存上的某个对应的地址。“页(page)”是虚拟内存空间向物理内存空间映射的基本单元。

下图1演示了虚拟内存空间和物理内存空间的相互关系,它们通过Page Table关联起来。其中虚拟内存空间中着色的部分分别被映射到物理内存空间对应相同着色的部分。而虚拟内存空间中灰色的部分表示在物理内存空间中没有与之对应的部分,也就是说灰色部分没有被映射到物理内存空间中。这么做也是本着“按需映射”的指导思想,因为虚拟内存空间很大,可能其中很多部分在一次程序运行过程中根本不需要访问,所以也就没有必要将虚拟内存空间中的这些部分映射到物理内存空间上。 到这里为止已经基本阐述了什么是虚拟内存了。总结一下就是,虚拟内存是一个假象的内存空间,在程序运行过程中虚拟内存空间中需要被访问的部分会被映射到物理内存空间中。虚拟内存空间大只能表示程序运行过程中可访问的空间比较大,不代表物理内存空间占用也大。

驻留内存 Resident Memory

驻留内存,顾名思义是指那些被映射到进程虚拟内存空间的物理内存。上图1中,在系统物理内存空间中被着色的部分都是驻留内存。比如,A1、A2、A3和A4是进程A的驻留内存;B1、B2和B3是进程B的驻留内存。进程的驻留内存就是进程实实在在占用的物理内存。一般我们所讲的进程占用了多少内存,其实就是说的占用了多少驻留内存而不是多少虚拟内存。因为虚拟内存大并不意味着占用的物理内存大。

物理地址空间和虚拟地址空间

首先,地址不过是一个整数而已,一旦这个整数被编码到CPU相应访存指令中的相关位段里,CPU就会把它放到地址总线上。这样CPU访问内存的时候,就会通过地址译码器获得这段整数信息,从而索引到具体的设备单元上。这个设备单元可以是设备寄存器,可以是内存单元。

那么地址空间其实就是一个这样的整数所表示的范围。具体落实到CPU电路上,就是地址总线位数所表示的数据范围。

比方说,CPU有8根地址线,它能编码2的8次方,即256个数据,地址0到地址255这个地址数据的范围,其实就是这个8位地址总线的CPU的地址空间;如果是32位地址总线的CPU,那么它地址的空间范围就是0~0xFFFFFFFF。从0到0xFFFFFFFF,这之间的每个整数编码就是一个地址,合起来就是地址空间。

那什么是内存地址空间呢?当然就是 能索引到内存单元的地址合集。我们再稍微扩展一下,你知道CPU的物理地址空间吗?其实它就是CPU地址总线位数所表示的数据范围,由于不同的CPU,甚至同一体系CPU的不同版本,其地址总线数设计实现不同,物理地址空间也是不同的。

聊完了物理地址空间,咱们当然还得说说虚拟地址空间。现在的计算机系统中,我们写的程序链接时的地址和运行时的地址,都使用了虚拟地址。

虚拟地址空间的大小和CPU中的一个设备MMU(内存管理单元)有关。虚拟地址之所以称为虚拟地址,是因为这种地址是假的,它不能真正索引到具体的设备单元,无论该单元属于设备寄存器还是内存,自然也就无法访问内存。还需要一个转换机构,把虚拟地址转换成真正的物理地址才能访问相应的设备。这个转换机构就是CPU的MMU,关于MMU的细节,这里我先卖个关子,放在后面的课程再说。

讲到这里,我们知道了,地址空间和我们所在的自然空间的寓意不是一样的,它们仅仅是为了表示某一位宽下的二进制数所有的编码合集。所谓内存地址空间,自然也就是内存地址编码的合集。

有了这个概念,我们就知道,程序指令在内存中是如何组织的,一旦我们的程序出现了问题我们就能精准地分析定位问题所在。同时,我们也明白了CPU如何通过地址访问内存,读取其中指令和数据,也就是CPU运行程序的基本逻辑机理。

关于虚拟内存和驻留内存这两个概念我们说到这里。下面一部分我们来看看top命令中VIRT、RES和SHR分别代表什么意思。

top命令中VIRT、RES和SHR的含义

搞清楚了虚拟内存的概念之后解释VIRT的含义就很简单了。VIRT表示的是进程虚拟内存空间大小。对应到图1中的进程A来说就是A1、A2、A3、A4以及灰色部分所有空间的总和。也就是说VIRT包含了在已经映射到物理内存空间的部分和尚未映射到物理内存空间的部分总和。

RES的含义是指进程虚拟内存空间中已经映射到物理内存空间的那部分的大小。对应到图1中的进程A来说就是A1、A2、A3以及A4几个部分空间的总和。所以说,看进程在运行过程中占用了多少内存应该看RES的值而不是VIRT的值。

最后来看看SHR所表示的含义。SHR是share(共享)的缩写,它表示的是进程占用的共享内存大小。在上图1中我们看到进程A虚拟内存空间中的A4和进程B虚拟内存空间中的B3都映射到了物理内存空间的A4/B3部分。咋一看很奇怪。为什么会出现这样的情况呢?其实我们写的程序会依赖于很多外部的动态库(.so),比如libc.so、libld.so等等。这些动态库在内存中仅仅会保存/映射一份,如果某个进程运行时需要这个动态库,那么动态加载器会将这块内存映射到对应进程的虚拟内存空间中。多个进展之间通过共享内存的方式相互通信也会出现这样的情况。这么一来,就会出现不同进程的虚拟内存空间会映射到相同的物理内存空间。这部分物理内存空间其实是被多个进程所共享的,所以我们将他们称为共享内存,用SHR来表示。某个进程占用的内存除了和别的进程共享的内存之外就是自己的独占内存了。所以要计算进程独占内存的大小只要用RES的值减去SHR值即可。

ps 命令找出PID

1
ps -ef | grep process

得到他的PID

ps -ef 的输出

1
UID          PID    PPID  C STIME TTY          TIME CMD

top 命令动态实时查看

top 命令动态实时的看到 CPU 和内存,然后按q键回到命令行

1
top -p pid

它的输出是动态的

1
2
3
4
5
6
7
Tasks:   1 total,   0 running,   1 sleeping,   0 stopped,   0 zombie
%Cpu(s):  1.7 us,  1.3 sy,  0.3 ni, 95.8 id,  0.9 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :  15712.7 total,    728.3 free,   9132.0 used,   5852.4 buff/cache
MiB Swap:  16212.0 total,  12481.7 free,   3730.2 used.   6325.0 avail Mem 

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND 
  34428 sense     20   0   33.0g 145220 113220 S   1.4   0.9   3:39.41 chrome 

也可以用ps -aux | grep pid查看

1
2
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
riske      34428  0.2  0.9 34619028 145372 ?     SLl  Mar28   3:38 /opt/google/chrome/chrome

1)USER: 行程拥有者 2)PID: 进程的ID 3)%CPU: 占用的 CPU 使用率 4)%MEM: 占用的记忆体使用率 5)VSZ: 占用的虚拟记忆体大小 6)RSS: 占用的记忆体大小 7)TTY: 终端的次要装置号码 (minor device number of tty) 8)STAT: 该行程的状态:

  • D: 不可中断的静止
  • R: 正在执行中
  • S: 静止状态
  • T: 暂停执行
  • Z: 不存在但暂时无法消除
  • W: 没有足够的记忆体分页可分配
  • <: 高优先序的行程
  • N: 低优先序的行程
  • L: 有记忆体分页分配并锁在记忆体内 9)START: 行程开始时间 10)TIME: 执行的时间 11)COMMAND:所执行的指令

ps -ef | grep chrome的输出

1
2
UID          PID    PPID  C STIME TTY          TIME CMD
riske      34428    5304  0 Mar28 ?        00:03:38 /opt/google/chrome/chrome

查看进程的 status 文件

1
cat /proc/34428/status

上面的输出如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
Name:        chrome
Umask:        0002
State:        S (sleeping)
Tgid:        34428
Ngid:        0
Pid:        34428
PPid:        5304
TracerPid:        0
Uid:        1001        1001        1001        1001
Gid:        1001        1001        1001        1001
FDSize:        512
Groups:        27 1001 1003 
NStgid:        34428
NSpid:        34428
NSpgid:        5686
NSsid:        5686
VmPeak:        34651952 kB
VmSize:        34619028 kB
VmLck:              16 kB
VmPin:               0 kB
VmHWM:          145520 kB
VmRSS:          145160 kB
RssAnon:           31928 kB
RssFile:          113012 kB
RssShmem:             220 kB
VmData:          415284 kB
VmStk:             132 kB
VmExe:          179308 kB
VmLib:           34312 kB
VmPTE:            1160 kB
VmSwap:           28052 kB
HugetlbPages:               0 kB
CoreDumping:        0
THP_enabled:        1
Threads:        37
SigQ:        0/62532
SigPnd:        0000000000000000
ShdPnd:        0000000000000000
SigBlk:        0000000000000000
SigIgn:        0000000000001000
SigCgt:        00000001c18144ff
CapInh:        0000000000000000
CapPrm:        0000000000000000
CapEff:        0000000000000000
CapBnd:        000001ffffffffff
CapAmb:        0000000000000000
NoNewPrivs:        0
Seccomp:        0
Seccomp_filters:        0
Speculation_Store_Bypass:        thread vulnerable
SpeculationIndirectBranch:        conditional enabled
Cpus_allowed:        ffff
Cpus_allowed_list:        0-15
Mems_allowed:        00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000001
Mems_allowed_list:        0
voluntary_ctxt_switches:        583271
nonvoluntary_ctxt_switches:        3764

在这里我们关注VmSize|VmRSS|VmData|VmStk|VmExe|VmLib 这个6个指标,下面有一些简单的解释。

  1. VmSize: 虚拟内存大小。 整个进程使用虚拟内存大小,是VmLib, VmExe, VmData, 和 VmStk的总和。占所有虚拟内存分配(文件映射,共享内存,堆内存,任何内存)的份额,并且几乎在每次分配新内存时都会增长。几乎,因为如果在数据段中用新的堆内存分配代替了释放的旧分配,则不会分配新的虚拟内存。每当释放虚拟分配时,它都会减少。VmPeak跟踪的最大值VmSize-只能随时间增加。
  2. VmLck: 虚拟内存锁。进程当前使用的并且加锁的虚拟内存总数
  3. VmRSS: 虚拟内存驻留集合大小。这是驻留在物理内存的一部分。它没有交换到硬盘。它包括代码,数据和栈。随着访问内存的增加而增加,随着将页面调出到交换设备的次数减少。
  4. VmData: 虚拟内存数据。堆使用的虚拟内存。随着使用堆的数据段部分而增长。由于当前的堆分配器会保留释放的内存,以防将来的分配需要它,它几乎永远不会收缩。
  5. VmStk: 虚拟内存栈。栈使用的虚拟内存
  6. VmExe: 可执行的虚拟内存,可执行的和静态链接库所使用的虚拟内存
  7. VmLib: 虚拟内存库,动态链接库所使用的虚拟内存 补充:
  8. VmPeak代表当前进程运行过程中占用内存的峰值.
  9. VmSize代表虚拟内存总大小
  10. VmLck代表进程已经锁住的物理内存的大小.锁住的物理内存不能交换到硬盘.
  11. VmHWM是程序得到分配到物理内存的峰值.
  12. VmRSS是程序现在使用的物理内存.
  13. VmData:表示进程数据段的大小.
  14. VmStk:表示进程堆栈段的大小.
  15. VmExe:表示进程代码的大小.
  16. VmLib:表示进程所使用LIB库的大小.
  17. VmPTE:占用的页表的大小.
  18. VmSwap:进程占用Swap的大小.
  19. Threads:表示当前进程组的线程数量.
  20. SigPnd:屏蔽位,存储了该线程的待处理信号,等同于线程的PENDING信号.
  21. ShnPnd:屏蔽位,存储了该线程组的待处理信号.等同于进程组的PENDING信号.
  22. SigBlk:存放被阻塞的信号,等同于BLOCKED信号.
  23. SigIgn:存放被忽略的信号,等同于IGNORED信号.
  24. SigCgt:存放捕获的信号,等同于CAUGHT信号.
  25. CapEff:当一个进程要进行某个特权操作时,操作系统会检查cap_effective的对应位是否有效,而不再是检查进程的有效UID是否为0.
  26. CapPrm:表示进程能够使用的能力,在cap_permitted中可以包含cap_effective中没有的能力,这些能力是被进程自己临时放弃的,也可以说cap_effective是cap_permitted的一个子集.
  27. CapInh:表示能够被当前进程执行的程序继承的能力.
  28. CapBnd:是系统的边界能力,我们无法改变它.
  29. Cpus_allowed:3指出该进程可以使用CPU的亲和性掩码,因为我们指定为两块CPU,所以这里就是3,如果该进程指定为4个CPU(如果有话),这里就是F(1111).
  30. Cpus_allowed_list:0-1指出该进程可以使用CPU的列表,这里是0-1.
  31. voluntary_ctxt_switches表示进程主动切换的次数.
  32. nonvoluntary_ctxt_switches表示进程被动切换的次数.

其他常用

ps aux | sort -k4,4nr | head -n 10查看内存占用前10名的程序

This post is licensed under CC BY 4.0 by the author.