如何解读top命令中进程的VIRT、RES、SHR内存参数?
理解top命令中VIRT、RES、SHR的含义及相关疑问解答
测试背景
我想搞懂top命令里进程的VIRT、RES、SHR三个数值的含义,于是写了一个未自行分配私有内存、仅调用系统函数的简单程序:
#include<unistd.h> #include<stdlib.h> #include<iostream> using namespace std; int main(int argc,char *argv[]){ while(1) { cout<<endl<<"This is a dummy program"<<endl; sleep(1); } exit(EXIT_SUCCESS); }
使用命令g++ -o Dummy1.exe -g -Wall Dummy.cpp生成可执行文件,运行后top输出如下:
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 4461 soumajit 20 0 13988 1772 1628 S 0.0 0.0 0:00.00 Dummy1.exe
疑问列表
- 构成SHR值的最小共享内存段有哪些?
- 未显式声明变量时,RES与SHR为何存在微小差异?
- VIRT的数值是如何分配的?
此外,捕获的strace输出中是否包含构成SHR值的共享内存段大小信息?
strace输出
brk(NULL) = 0x561940984000 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=130951, ...}) = 0 mmap(NULL, 130951, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f54a6877000 close(3) = 0 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/libstdc++.so.6", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\220\304\10\0\0\0\0\0"..., 832) = 832 fstat(3, {st_mode=S_IFREG|0644, st_size=1594864, ...}) = 0 mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f54a6875000 mmap(NULL, 3702848, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f54a62e7000 mprotect(0x7f54a6460000, 2097152, PROT_NONE) = 0 mmap(0x7f54a6660000, 49152, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x179000) = 0x7f54a6660000 mmap(0x7f54a666c000, 12352, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f54a666c000 close(3) = 0 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\260\34\2\0\0\0\0\0"..., 832) = 832 fstat(3, {st_mode=S_IFREG|0755, st_size=2030544, ...}) = 0 mmap(NULL, 4131552, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f54a5ef6000 mprotect(0x7f54a60dd000, 2097152, PROT_NONE) = 0 mmap(0x7f54a62dd000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7f54a62dd000 mmap(0x7f54a62e3000, 15072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f54a62e3000 close(3) = 0 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libm.so.6", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\200\272\0\0\0\0\0\0"..., 832) = 832 fstat(3, {st_mode=S_IFREG|0644, st_size=1700792, ...}) = 0 mmap(NULL, 3789144, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f54a5b58000 mprotect(0x7f54a5cf5000, 2093056, PROT_NONE) = 0 mmap(0x7f54a5ef4000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x19c000) = 0x7f54a5ef4000 close(3) = 0 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libgcc_s.so.1", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\300*\0\0\0\0\0\0"..., 832) = 832 fstat(3, {st_mode=S_IFREG|0644, st_size=96616, ...}) = 0 mmap(NULL, 2192432, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f54a5940000 mprotect(0x7f54a5957000, 2093056, PROT_NONE) = 0 mmap(0x7f54a5b56000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x16000) = 0x7f54a5b56000 close(3) = 0 mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f54a6873000 arch_prctl(ARCH_SET_FS, 0x7f54a6873d00) = 0 mprotect(0x7f54a62dd000, 16384, PROT_READ) = 0 mprotect(0x7f54a5b56000, 4096, PROT_READ) = 0 mprotect(0x7f54a5ef4000, 4096, PROT_READ) = 0 mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f54a6871000 mprotect(0x7f54a6660000, 40960, PROT_READ) = 0 mprotect(0x56193f2a2000, 4096, PROT_READ) = 0 mprotect(0x7f54a6897000, 4096, PROT_READ) = 0 munmap(0x7f54a6877000, 130951) = 0 brk(NULL) = 0x561940984000 brk(0x5619409a5000) = 0x5619409a5000 fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 3), ...}) = 0 write(1, "\n", 1) = 1 write(1, "This is dummy program\n", 22) = 22 nanosleep({tv_sec=1, tv_nsec=0}, {tv_sec=0, tv_nsec=203523438}) = ? ERESTART_RESTARTBLOCK (Interrupted by signal) --- SIGINT {si_signo=SIGINT, si_code=SI_KERNEL} --- +++ killed by SIGINT +++
测试环境
Linux soumajit-HP-Pavilion-Desktop-590-p0xxx 5.4.0-71-generic #79~18.04.1-Ubuntu SMP Thu Mar 25 05:45:39 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
疑问解答
先明确三个值的基础定义
- VIRT:进程虚拟地址空间的总大小,涵盖所有已分配的虚拟内存区域,不管是否加载到物理内存,也不管是私有还是共享属性。
- RES:进程实际占用的物理内存(常驻内存),不包含交换到磁盘的部分。
- SHR:进程占用的共享内存大小,这部分内存可被多个进程共享,节省物理内存开销。
1. 构成SHR值的最小共享内存段有哪些?
你的程序中,SHR主要由以下几类共享内存段构成:
- 系统动态链接库的只读/执行段:比如
libc.so.6、libstdc++.so.6、libm.so.6、libgcc_s.so.1这些库的可执行代码段(.text)和只读数据段(.rodata),它们会被多个进程共享,不会每个进程都加载一份物理副本。 - 动态链接器(ld.so)的共享部分:动态链接器本身也是一个共享库,它的代码和只读数据同样属于共享内存,会被计入SHR。
- 系统层面的其他共享只读数据结构:比如内核提供的某些共享配置页,但占比极小,主要贡献还是来自上述动态库。
2. 未显式声明变量时,RES与SHR为何存在微小差异?
RES = SHR + 进程私有物理内存。哪怕你没写任何变量,进程依然会有私有内存页:
- 进程栈空间:每个进程都有独立的栈,用来存储函数调用上下文、寄存器状态等,main函数启动时就会创建栈帧,这部分是完全私有的,不会共享。
- 动态库的写时复制(COW)私有页:动态库的全局变量默认是共享的,但如果进程修改了它,内核会复制该页生成私有副本,这部分会从SHR转为私有内存,计入RES但不计入SHR。
- 堆的初始私有页:从strace的
brk(0x5619409a5000)可以看到,进程启动时会分配初始堆空间,哪怕没主动分配内存,这部分页也是私有的。
这些私有内存的总和就是RES和SHR的差值(1772KB - 1628KB = 144KB左右)。
3. VIRT的数值是如何分配的?
VIRT是进程所有虚拟内存区域的总和,你的程序里13988KB的VIRT来自这些部分:
- 程序自身的可执行文件映射:编译生成的Dummy1.exe的代码段、只读数据段等会被映射到虚拟地址空间。
- 动态链接库的虚拟映射区域:每个加载的动态库都会占用一段虚拟地址空间,哪怕只有部分代码加载到物理内存,虚拟空间会预先分配完整的范围(比如libc.so.6的虚拟映射大小约4MB)。
- 栈的虚拟空间:默认栈大小通常是8MB,系统会预先分配整个虚拟范围,实际只用到很小一部分。
- 堆的虚拟空间:通过brk系统调用扩展的堆区域,strace里堆扩展了约132KB。
- 匿名虚拟映射:比如strace里的
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0),这些是私有匿名映射,用来承载动态库的BSS段或临时内存。
虚拟地址空间是预分配的,不需要对应实际物理内存,所以VIRT通常远大于RES。
strace输出中是否包含构成SHR的共享内存段大小信息?
是的,strace里的mmap调用可以找到相关信息:
- 加载动态库时的
MAP_PRIVATE|MAP_DENYWRITE类型映射(比如加载libc.so.6时的mmap(NULL, 4131552, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0)),这些映射的只读/执行段是共享物理内存的,大小会被计入SHR。注意MAP_PRIVATE映射在未被修改时是共享的,只有可写段会触发写时复制转为私有。 - 另外,动态链接器加载的
/etc/ld.so.cache的映射(mmap(NULL, 130951, PROT_READ, MAP_PRIVATE, 3, 0))后来被munmap释放了,所以不会计入最终的SHR。
你可以通过每个动态库的mmap总大小,减去其中可写私有段的大小(比如libc的mmap(0x7f54a62dd000, 24576, PROT_READ|PROT_WRITE, ...)),剩下的只读/执行部分就是共享内存的大小,对应SHR中的贡献。
内容的提问来源于stack exchange,提问作者Soumajit
相关产品推荐
相关产品推荐

