Linux内存管理中的RSS和VSZ是什么
- 2024-10-09 09:11:00
- admin 原创
- 81
问题描述:
Linux 内存管理中的 RSS 和 VSZ 是什么?在多线程环境中如何管理和跟踪这两者?
解决方案 1:
RSS 是驻留集大小,用于显示分配给该进程的内存量以及 RAM 中的内存量。它不包括换出的内存。它包括共享库中的内存,只要这些库中的页面确实在内存中。它包括所有堆栈和堆内存。
VSZ 是虚拟内存大小。它包括进程可以访问的所有内存,包括换出的内存、已分配但未使用的内存以及来自共享库的内存。
因此,如果进程 A 有一个 500K 的二进制文件并链接到 2500K 的共享库,有 200K 的堆栈/堆分配,其中 100K 实际在内存中(其余的是交换或未使用的),并且它实际上只加载了 1000K 的共享库和 400K 自己的二进制文件,那么:
RSS: 400K + 1000K + 100K = 1500K
VSZ: 500K + 2500K + 200K = 3200K
由于部分内存是共享的,许多进程可能会使用它,所以如果将所有 RSS 值加起来,您最终很容易得到比系统拥有的更多的空间。
分配的内存也可能不存在于 RSS 中,直到程序实际使用时才存在。因此,如果您的程序预先分配了大量内存,然后随着时间的推移使用它,您可能会看到 RSS 上升而 VSZ 保持不变。
还有 PSS(比例集大小)。这是一种较新的度量,它跟踪当前进程使用的共享内存的比例。因此,如果有两个进程使用之前相同的共享库:
PSS: 400K + (1000K/2) + 100K = 400K + 500K + 100K = 1000K
所有线程都共享相同的地址空间,因此每个线程的 RSS、VSZ 和 PSS 与进程中的所有其他线程相同。在 linux/unix 中使用 ps 或 top 查看此信息。
其实还有很多内容,要了解更多,请查看以下参考资料:
另请参阅:
确定进程“真实”内存使用情况的方法,即私人脏 RSS?
解决方案 2:
RSS 是驻留集大小(物理驻留内存 - 当前占用机器物理内存的空间),VSZ 是虚拟内存大小(分配的地址空间 - 在进程的内存映射中分配了地址,但现在其背后不一定有任何实际内存)。
请注意,在当今常见的虚拟机中,从机器角度来看的物理内存可能并不是真正的物理内存。
解决方案 3:
最小可运行示例
为了理解这一点,您必须了解分页的基础知识:x86 分页如何工作?特别是,操作系统可以在 RAM 或磁盘上实际拥有备用存储(RSS 常驻内存)之前通过页表/其内部内存簿记录(VSZ 虚拟内存)分配虚拟内存。
现在为了观察实际运行情况,让我们创建一个程序:
分配比物理内存更多的 RAM
mmap
在每个页面上写入一个字节,以确保每个页面都从虚拟内存 (VSZ) 转到实际使用的内存 (RSS)
使用以下方法之一检查进程的内存使用情况:C 中当前进程的内存使用情况
主程序
#define _GNU_SOURCE
#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
typedef struct {
unsigned long size,resident,share,text,lib,data,dt;
} ProcStatm;
/* https://stackoverflow.com/questions/1558402/memory-usage-of-current-process-in-c/7212248#7212248 */
void ProcStat_init(ProcStatm *result) {
const char* statm_path = "/proc/self/statm";
FILE *f = fopen(statm_path, "r");
if(!f) {
perror(statm_path);
abort();
}
if(7 != fscanf(
f,
"%lu %lu %lu %lu %lu %lu %lu",
&(result->size),
&(result->resident),
&(result->share),
&(result->text),
&(result->lib),
&(result->data),
&(result->dt)
)) {
perror(statm_path);
abort();
}
fclose(f);
}
int main(int argc, char **argv) {
ProcStatm proc_statm;
char *base, *p;
char system_cmd[1024];
long page_size;
size_t i, nbytes, print_interval, bytes_since_last_print;
int snprintf_return;
/* Decide how many ints to allocate. */
if (argc < 2) {
nbytes = 0x10000;
} else {
nbytes = strtoull(argv[1], NULL, 0);
}
if (argc < 3) {
print_interval = 0x1000;
} else {
print_interval = strtoull(argv[2], NULL, 0);
}
page_size = sysconf(_SC_PAGESIZE);
/* Allocate the memory. */
base = mmap(
NULL,
nbytes,
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS,
-1,
0
);
if (base == MAP_FAILED) {
perror("mmap");
exit(EXIT_FAILURE);
}
/* Write to all the allocated pages. */
i = 0;
p = base;
bytes_since_last_print = 0;
/* Produce the ps command that lists only our VSZ and RSS. */
snprintf_return = snprintf(
system_cmd,
sizeof(system_cmd),
"ps -o pid,vsz,rss | awk '{if (NR == 1 || $1 == \"%ju\") print}'",
(uintmax_t)getpid()
);
assert(snprintf_return >= 0);
assert((size_t)snprintf_return < sizeof(system_cmd));
bytes_since_last_print = print_interval;
do {
/* Modify a byte in the page. */
*p = i;
p += page_size;
bytes_since_last_print += page_size;
/* Print process memory usage every print_interval bytes.
* We count memory using a few techniques from:
* https://stackoverflow.com/questions/1558402/memory-usage-of-current-process-in-c */
if (bytes_since_last_print > print_interval) {
bytes_since_last_print -= print_interval;
printf("extra_memory_committed %lu KiB
", (i * page_size) / 1024);
ProcStat_init(&proc_statm);
/* Check /proc/self/statm */
printf(
"/proc/self/statm size resident %lu %lu KiB
",
(proc_statm.size * page_size) / 1024,
(proc_statm.resident * page_size) / 1024
);
/* Check ps. */
puts(system_cmd);
system(system_cmd);
puts("");
}
i++;
} while (p < base + nbytes);
/* Cleanup. */
munmap(base, nbytes);
return EXIT_SUCCESS;
}
GitHub 上游。
编译并运行:
gcc -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
echo 1 | sudo tee /proc/sys/vm/overcommit_memory
sudo dmesg -c
./main.out 0x1000000000 0x200000000
echo $?
sudo dmesg
在哪里:
0x1000000000 == 64GiB:是我的计算机物理 RAM 的 2 倍,为 32GiB
0x200000000 == 8GiB:每 8GiB 打印一次内存,因此在崩溃前我们应该在 32GiB 左右获得 4 次打印
echo 1 | sudo tee /proc/sys/vm/overcommit_memory
:Linux 允许我们进行大于物理 RAM 的 mmap 调用所需的内存:malloc 可以分配的最大内存
程序输出:
extra_memory_committed 0 KiB
/proc/self/statm size resident 67111332 768 KiB
ps -o pid,vsz,rss | awk '{if (NR == 1 || $1 == "29827") print}'
PID VSZ RSS
29827 67111332 1648
extra_memory_committed 8388608 KiB
/proc/self/statm size resident 67111332 8390244 KiB
ps -o pid,vsz,rss | awk '{if (NR == 1 || $1 == "29827") print}'
PID VSZ RSS
29827 67111332 8390256
extra_memory_committed 16777216 KiB
/proc/self/statm size resident 67111332 16778852 KiB
ps -o pid,vsz,rss | awk '{if (NR == 1 || $1 == "29827") print}'
PID VSZ RSS
29827 67111332 16778864
extra_memory_committed 25165824 KiB
/proc/self/statm size resident 67111332 25167460 KiB
ps -o pid,vsz,rss | awk '{if (NR == 1 || $1 == "29827") print}'
PID VSZ RSS
29827 67111332 25167472
Killed
退出状态:
137
根据128 + 信号号规则,意味着我们得到了信号号9
,也man 7 signal
就是SIGKILL,它是由 Linux内存不足杀手发送的。
输出解释:
mmap 之后,VSZ 虚拟内存保持不变`printf '0x%X
' 0x40009A4 KiB ~= 64GiB(值以 KiB 为单位)。
ps`RSS“实际内存使用量”仅在我们触摸页面时才会缓慢增加。例如:
第一次打印时,我们有
extra_memory_committed 0
,这意味着我们还没有触及任何页面。RSS 是一个很小的部分1648 KiB
,它被分配给正常的程序启动,如文本区域、全局变量等。在第二次打印时,我们写入了
8388608 KiB == 8GiB
页数。结果,RSS 增加了 8GIB,达到8390256 KiB == 8388608 KiB + 1648 KiB
RSS 继续以 8GiB 的增量增加。最后一次打印显示大约有 24 GiB 的内存,在打印出 32 GiB 之前,OOM 终止程序终止了该进程
另请参阅:https://unix.stackexchange.com/questions/35129/need-explanation-on-resident-set-size-virtual-size
OOM killer 日志
我们的dmesg
命令已经显示了 OOM killer 日志。
有人要求对这些内容进行准确解释:
了解 Linux oom-killer 的日志,但让我们在这里快速看一下。
https://serverfault.com/questions/548736/how-to-read-oom-killer-syslog-messages
日志的第一行是:
[ 7283.479087] mongod invoked oom-killer: gfp_mask=0x6200ca(GFP_HIGHUSER_MOVABLE), order=0, oom_score_adj=0
因此,我们发现有趣的是,正是在我的笔记本电脑后台始终运行的 MongoDB 守护进程首先触发了 OOM 杀手,大概是当这个可怜的东西试图分配一些内存时。
然而,OOM 杀手并不一定会杀死唤醒它的人。
调用后,内核打印一个表或多个进程,包括oom_score
:
[ 7283.479292] [ pid ] uid tgid total_vm rss pgtables_bytes swapents oom_score_adj name
[ 7283.479303] [ 496] 0 496 16126 6 172032 484 0 systemd-journal
[ 7283.479306] [ 505] 0 505 1309 0 45056 52 0 blkmapd
[ 7283.479309] [ 513] 0 513 19757 0 57344 55 0 lvmetad
[ 7283.479312] [ 516] 0 516 4681 1 61440 444 -1000 systemd-udevd
进一步说,我们看到我们自己的小东西main.out
实际上在之前的调用中被杀死了:
[ 7283.479871] Out of memory: Kill process 15665 (main.out) score 865 or sacrifice child
[ 7283.479879] Killed process 15665 (main.out) total-vm:67111332kB, anon-rss:92kB, file-rss:4kB, shmem-rss:30080832kB
[ 7283.479951] oom_reaper: reaped process 15665 (main.out), now anon-rss:0kB, file-rss:0kB, shmem-rss:30080832kB
该日志提到score 865
该进程具有最高(最差)的 OOM 杀手分数,如:https: //unix.stackexchange.com/questions/153585/how-does-the-oom-killer-decide-which-process-to-kill-first
同样有趣的是,一切显然都发生得如此之快,以至于在释放的内存被占用之前,它就oom
被该过程再次唤醒DeadlineMonitor
:
[ 7283.481043] DeadlineMonitor invoked oom-killer: gfp_mask=0x6200ca(GFP_HIGHUSER_MOVABLE), order=0, oom_score_adj=0
这次会杀死一些 Chromium 进程,这些进程通常是我电脑的正常内存占用者:
[ 7283.481773] Out of memory: Kill process 11786 (chromium-browse) score 306 or sacrifice child
[ 7283.481833] Killed process 11786 (chromium-browse) total-vm:1813576kB, anon-rss:208804kB, file-rss:0kB, shmem-rss:8380kB
[ 7283.497847] oom_reaper: reaped process 11786 (chromium-browse), now anon-rss:0kB, file-rss:0kB, shmem-rss:8044kB
在 Ubuntu 19.04、Linux 内核 5.0.0 中测试。
Linux 内核文档
https://github.com/torvalds/linux/blob/v5.17/Documentation/filesystems/proc.rst有一些要点。那里没有使用术语“VSZ”,但使用了“RSS”,而且没有什么太有启发性的东西(惊讶吗?!)
内核似乎使用了术语VmSize
,而不是 VSZ ,例如/proc/$PID/status
。
一些有趣的引言:
这些行中的第一行显示的信息与 /proc/PID/maps 中映射的信息相同。接下来的几行显示映射的大小 (size);支持 VMA 时分配的每个页面的大小 (KernelPageSize),通常与页表条目中的大小相同;支持 VMA 时 MMU 使用的页面大小(在大多数情况下,与 KernelPageSize 相同);当前驻留在 RAM 中的映射量 (RSS);进程在此映射中所占的比例 (PSS);以及映射中干净和脏的共享和私有页面的数量。
进程的“比例集大小”(PSS) 是其在内存中的页面数,其中每个页面除以共享它的进程数。因此,如果一个进程有 1000 个页面属于自己,另外 1000 个页面与另一个进程共享,则其 PSS 将为 1500。
请注意,即使某个页面是 MAP_SHARED 映射的一部分,但只映射了一个 pte,即当前仅由一个进程使用,也被视为私有的,而不是共享的。
因此我们可以猜测更多的事情:
单个进程使用的共享库出现在 RSS 中,如果多个进程拥有它们,则不会
jmh 提到了PSS ,它在“我是唯一持有共享库的进程”和“有 N 个进程持有共享库,因此每个进程平均持有内存/N”之间采用了更成比例的方法
解决方案 4:
VSZ——虚拟场景大小
虚拟集大小是在初始执行期间分配给进程(程序)的内存大小。虚拟集大小内存只是一个进程可用于执行的内存量的数字。
RSS - 常驻集大小(类似 RAM)
与 VSZ(虚拟集大小)相反,RSS 是进程当前使用的内存。这是当前进程正在使用的实际 RAM 数量(以千字节为单位)。
来源
解决方案 5:
我认为关于 RSS 与 VSZ 的讨论已经很多了。从管理员/程序员/用户的角度来看,当我设计/编写应用程序时,我更关心 RSZ(常驻内存),因为当您不断拉取越来越多的变量(堆积)时,您会看到这个值急剧上升。尝试一个简单的程序来循环构建基于 malloc 的空间分配,并确保在该 malloc 空间中填充数据。RSS 不断上升。就 VSZ 而言,它更像是 Linux 所做的虚拟内存映射,其核心功能之一源自传统操作系统概念。VSZ 管理由内核的虚拟内存管理完成,有关 VSZ 的更多信息,请参阅 Robert Love 对 mm_struct 和 vm_struct 的描述,它们是内核中基本 task_struct 数据结构的一部分。
解决方案 6:
总结一下@jmh 的优秀答案:
在#linux中,一个进程的内存包括:
它自己的二进制文件
其共享库
它的堆栈和堆
由于分页,并非所有这些内容始终都位于内存中,只有有用的、最近使用的部分(页面)才会位于内存中。其他部分将被调出(或交换出)以便为其他进程腾出空间。
下表取自@jmh 的回答,展示了特定进程的常驻内存和虚拟内存的示例。
+-------------+-------------------------+------------------------+
| portion | actually in memory | total (allocated) size |
|-------------+-------------------------+------------------------|
| binary | 400K | 500K |
| shared libs | 1000K | 2500K |
| stack+heap | 100K | 200K |
|-------------+-------------------------+------------------------|
| | RSS (Resident Set Size) | VSZ (Virtual Set Size) |
|-------------+-------------------------+------------------------|
| | 1500K | 3200K |
+-------------+-------------------------+------------------------+
总结一下:常驻内存是当前实际位于物理内存中的内存,而虚拟大小是加载所有组件所需的总物理内存。
当然,数字加起来不合理,因为库在多个进程之间共享,并且它们的内存是每个进程单独计算的,即使它们只加载一次。
解决方案 7:
它们不受管理,但可以测量,并可能受到限制(请参阅getrlimit
系统调用,也参见getrlimit(2))。
RSS 表示常驻集大小(虚拟地址空间在 RAM 中的部分)。
您可以使用proc(5)查询进程 1234 的虚拟地址空间及其状态(包括内存消耗)cat /proc/1234/maps
`cat /proc/1234/status`
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件