在Linux下,当你使用top、ps等命令查看某个进程的显存使用情况时,经常会看到VIRT、RES、SHR等,它们是什么意思呢? 不同尺寸对工艺有什么影响?
查看进程使用的显存
在进程看来,所有的显存都是虚拟显存,但是这个虚拟显存对应多少数学显存呢? 我们在Linux显存管理中介绍过,并不是每一块虚拟显存都有对应的数学显存,对应的数据可能在c盘的某个文件中,也可能在swap空间的某个区域中。 只有内核知道进程真正的数学内存使用情况,我们只能通过内核打开的一些套接字来获取这些统计数据。
顶部
先看top的输出(top使用的数据来自/proc/[pid]/statm),这里只是摘录了一些数据:
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
2530 root 20 0 0 0 0 S 0.3 0.0 0:02.69 kworker/0:0
2714 dev 20 0 41824 3700 3084 R 0.3 0.7 0:00.02 top
3008 dev 20 0 22464 5124 3356 S 0.0 1.0 0:00.02 bash
VIRT:进程使用的虚拟显存大小
RES:系统为虚拟内存分配的数学内存大小,包括文件内存和显存,其中包括进程自身分配使用的显存,以及通过mmap与其他进程共享的显存; 而file的显存是指加载可执行文件和动态库占用的显存,以及通过方法调用mmap映射文件使用的显存(当显存和文件没有写回怎么看物理内存,那么这部分显存就完成了),这部分显存也有可能被其他进程共享。
SHR:RES的一部分,表示与其他进程共享的显存,包括mmap共享的显存和file的显存。 通过priv方法调用mmap映射文件时,如果文件内容没有改变,那么那部分内容就是SHR。 一旦文件内容改变,文件没有回写,那么这部分显存是也不是SHR。
%MEM:等于RES/total*100%,其中total是指总的数学内存大小。
注意:由于SHR可能被多个进程共享,所以系统中所有进程的RES总和可能会超过数学内存的总数。 同理,所有进程的%MEM之和可能会超过100%。
从里面分析可以看出,VIRT的参考值意义不大,它只能反映程序的大小,而RES并不能完全代表一个进程实际占用的显存空间,因为它还包括了SHR的一部分,比如三个bash进程共享一个libc动态库,那么libc占用的显存归谁所有呢? 三个过程是否平分? 如果启动一个bash占用4M的RES,其中3M被libc占用,因为三个进程共享libc的3M,所以启动三个bash实际占用的显存为3*(4-3)+3=6M ,而如果简单的按照RES计算的话,三个进程会使用12M的空间。 因此,理解RES和SHR这两个数据的含义,对于我们评估一台服务器能运行多少个进程就显得尤为重要。 不要一听进程占用20M就认为系统能运行的进程数就是数学上的总内存。 数字乘以20M,虽然20M中有很大一部分可能是SHR。
注意:top 命令输出中的 RES 和 pmap 输出中的 RSS 是一回事。
地图
里面的top命令只显示一个进程占用了多少显存,而pmap可以更详细的显示谁在占用显存。 pmap 命令的输出来自 /proc/[pid]/maps 和 /proc/[pid]/smaps 这两个文件。 第一个文件包含每个段的大致描述,后一个文件包含更详细的描述。 信息。
这里使用pmap查看当前bash内存使用情况:
#这里$$表示当前bash的进程ID,下面只展示部分输出结果
dev@dev:~$ pmap $$
2805: bash
0000000000400000 976K r-x-- bash
00000000006f3000 4K r---- bash
00000000006f4000 36K rw--- bash
00000000006fd000 24K rw--- [ anon ]
0000000000be4000 1544K rw--- [ anon ]
......
00007f1fa0e9e000 2912K r---- locale-archive
00007f1fa1176000 1792K r-x-- libc-2.23.so
00007f1fa1336000 2044K ----- libc-2.23.so
00007f1fa1535000 16K r---- libc-2.23.so
00007f1fa1539000 8K rw--- libc-2.23.so
00007f1fa153b000 16K rw--- [ anon ]
......
00007f1fa196c000 152K r-x-- ld-2.23.so
00007f1fa1b7e000 28K r--s- gconv-modules.cache
00007f1fa1b85000 16K rw--- [ anon ]
00007f1fa1b8f000 8K rw--- [ anon ]
00007f1fa1b91000 4K r---- ld-2.23.so
00007f1fa1b92000 4K rw--- ld-2.23.so
00007f1fa1b93000 4K rw--- [ anon ]
00007ffde903a000 132K rw--- [ stack ]
00007ffde90e4000 8K r---- [ anon ]
00007ffde90e6000 8K r-x-- [ anon ]
ffffffffff600000 4K r-x-- [ anon ]
total 22464K
这里第一列是显存的起始地址,第二列是地址大小,第三列是这个显存的访问权限,最后一列是要访问的文件。 这里的地址都是虚拟地址,size也是虚拟地址的大小。
这里的输出有很多[anon]行,说明c盘没有对应的文件,一般是可执行文件或者动态库中的bss段。 其实可能还有对应的文件,比如文件的data段。 程序的data段和bss段的介绍可以参考elf的相关资料。
里面可以看到bash、libc-2.23.so等文件有多行,但是每一行的权限是不同的。 这是因为每个动态库或可执行文件都被分成很多段,有些只能读取和执行。 在代码段中,有一个可以读写的数据段,里面有一行如“16Krw—[anon]”怎么看物理内存,里面是libc-2.23.so的bss段一行。
[stack]表示进程使用的栈空间,这里看不到堆,因为pmap默认不单独标示堆,因为堆是,所以从这里的大小可以推断出堆是" —[匿名]”。
虽然从前面的结果中无法看出每个实际占用了多少化学显存,但是为了看到RSS,需要使用-X参数,看下面更详细的输出:
dev@dev:~$ pmap -X $$
2805: bash
Address Perm Offset Device Inode Size Rss Pss Referenced Anonymous Shared_Hugetlb Private_Hugetlb Swap SwapPss Locked Mapping
00400000 r-xp 00000000 fc:00 390914 976 888 526 888 0 0 0 0 0 0 bash
006f3000 r--p 000f3000 fc:00 390914 4 4 4 4 4 0 0 0 0 0 bash
006f4000 rw-p 000f4000 fc:00 390914 36 36 36 36 36 0 0 0 0 0 bash
006fd000 rw-p 00000000 00:00 0 24 24 24 24 24 0 0 0 0 0
00be4000 rw-p 00000000 00:00 0 1544 1544 1544 1544 1544 0 0 0 0 0 [heap]
.....
7f1fa0e9e000 r--p 00000000 fc:00 136340 2912 400 83 400 0 0 0 0 0 0 locale-archive
7f1fa1176000 r-xp 00000000 fc:00 521726 1792 1512 54 1512 0 0 0 0 0 0 libc-2.23.so
7f1fa1336000 ---p 001c0000 fc:00 521726 2044 0 0 0 0 0 0 0 0 0 libc-2.23.so
7f1fa1535000 r--p 001bf000 fc:00 521726 16 16 16 16 16 0 0 0 0 0 libc-2.23.so
7f1fa1539000 rw-p 001c3000 fc:00 521726 8 8 8 8 8 0 0 0 0 0 libc-2.23.so
7f1fa153b000 rw-p 00000000 00:00 0 16 12 12 12 12 0 0 0 0 0
......
7f1fa196c000 r-xp 00000000 fc:00 521702 152 144 4 144 0 0 0 0 0 0 ld-2.23.so
7f1fa1b7e000 r--s 00000000 fc:00 132738 28 28 9 28 0 0 0 0 0 0 gconv-modules.cache
7f1fa1b85000 rw-p 00000000 00:00 0 16 16 16 16 16 0 0 0 0 0
7f1fa1b8f000 rw-p 00000000 00:00 0 8 8 8 8 8 0 0 0 0 0
7f1fa1b91000 r--p 00025000 fc:00 521702 4 4 4 4 4 0 0 0 0 0 ld-2.23.so
7f1fa1b92000 rw-p 00026000 fc:00 521702 4 4 4 4 4 0 0 0 0 0 ld-2.23.so
7f1fa1b93000 rw-p 00000000 00:00 0 4 4 4 4 4 0 0 0 0 0
7ffde903a000 rw-p 00000000 00:00 0 136 24 24 24 24 0 0 0 0 0 [stack]
7ffde90e4000 r--p 00000000 00:00 0 8 0 0 0 0 0 0 0 0 0 [vvar]
7ffde90e6000 r-xp 00000000 00:00 0 8 4 0 4 0 0 0 0 0 0 [vdso]
ffffffffff600000 r-xp 00000000 00:00 0 4 0 0 0 0 0 0 0 0 0 [vsyscall]
===== ==== ==== ========== ========= ============== =============== ==== ======= ======
22468 5084 2578 5084 1764 0 0 0 0 0 KB
权限数组有一个额外的标记 s 和 p。 s表示是与他人共享的显存空间。 读写会影响其他进程,p表示这是自己私有的显存空间。 读写这部分显存不会影响其他进程。 过程导致影响。
输出标识了[heap]段,也解释了前面[anon]的含义(vvar,vdso,是映射到内核的特殊段),空数组是文件段上一行的bss(但是gconv-.cache旁边有两行,可能跟共享显存有关,不详述)。
这些列标识大小大于或等于 RSS 的形式映射数学内存是什么以及多少
RSS栏表示实际占用的数学内存大小
top命令输出的SHR显存
最后我们看看pmap的输出是由top命令输出的SHR组成的。
dev@dev:~$ pmap -d $$
3108: bash
Address Kbytes Mode Offset Device Mapping
0000000000400000 976 r-x-- 0000000000000000 0fc:00000 bash
00000000006f3000 4 r---- 00000000000f3000 0fc:00000 bash
00000000006f4000 36 rw--- 00000000000f4000 0fc:00000 bash
00000000006fd000 24 rw--- 0000000000000000 000:00000 [ anon ]
0000000000c23000 1544 rw--- 0000000000000000 000:00000 [ anon ]
......
00007f53af18e000 16 rw--- 0000000000000000 000:00000 [ anon ]
00007f53af198000 8 rw--- 0000000000000000 000:00000 [ anon ]
00007f53af19a000 4 r---- 0000000000025000 0fc:00000 ld-2.23.so
00007f53af19b000 4 rw--- 0000000000026000 0fc:00000 ld-2.23.so
00007f53af19c000 4 rw--- 0000000000000000 000:00000 [ anon ]
00007ffc5a94b000 132 rw--- 0000000000000000 000:00000 [ stack ]
00007ffc5a9b7000 8 r---- 0000000000000000 000:00000 [ anon ]
00007ffc5a9b9000 8 r-x-- 0000000000000000 000:00000 [ anon ]
ffffffffff600000 4 r-x-- 0000000000000000 000:00000 [ anon ]
mapped: 22464K writeable/private: 1848K shared: 28K
dev@dev:~$ top -p $$
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
3108 dev 20 0 22464 5028 3264 S 0.0 1.0 0:00.02 bash
从里面的输出可以看出SHR≈RES-/,其中/主要包括栈和堆,以及可执行文件和动态库的data和bss段,stack+heap=1544+132=1675,其中已经占了绝大部分,所以data和bss段基本可以忽略,所以一般情况下,SHR≈RES-[heap]-[stack],因为stack通常比较小,前面的等式可以进一步约等于:SHR≈RES-[heap]。
总结
top命令可以看到一个进程占用的虚拟显存空间、物理显存空间以及与其他进程共享的化学显存空间。 这里的共享空间包括通过mmap共享的显存、共享的可执行文件和动态库。 mmap命令可以看到更详细的信息,比如可执行文件的大小和链接的动态库,化学显存占用了哪些段。
进程占用的虚拟地址空间的大小与程序的大小有关。 不仅仅是栈和堆段,其他段的大小基本都是固定的,但是在程序链接的时候就已经确定了,所以基本上只需要关注栈和堆段就可以了,因为stack相对于heap来说是比较小的,所以只要没有stack的异常,只需要关注heap即可。
在实际工作过程中,虽然我们比较关心RSS用了多少,谁在用,简单来说,如果我们不同时启动多个进程(同一个程序),RSS是一个很好的实用工具 显存使用一个参考值,但是如果你像那样同时运行多个进程,那么将RSS占用的空间除以SHR是实际化学显存占用的一个很好的参考值。 其实这些都是大概的计算值。
仍然很难准确评估一个进程占用多少显存。 需要深入了解每个环节的流程,尤其是SHR部分共享哪些流程。 但是现在服务器上的显存都是以G为单位的,所以一般情况下,一个大概的计算和合理的测试就可以满足我们的需求。