You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何解读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

疑问列表

  1. 构成SHR值的最小共享内存段有哪些?
  2. 未显式声明变量时,RES与SHR为何存在微小差异?
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 03:42:03