Linux内存管理中的RSS和VSZ是什么

2024-10-09 09:11:00
admin
原创
80
摘要:问题描述:Linux 内存管理中的 RSS 和 VSZ 是什么?在多线程环境中如何管理和跟踪这两者?解决方案 1:RSS 是驻留集大小,用于显示分配给该进程的内存量以及 RAM 中的内存量。它不包括换出的内存。它包括共享库中的内存,只要这些库中的页面确实在内存中。它包括所有堆栈和堆内存。VSZ 是虚拟内存大小...

问题描述:

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 虚拟内存)分配虚拟内存。

现在为了观察实际运行情况,让我们创建一个程序:

  • 分配比物理内存更多的 RAMmmap

  • 在每个页面上写入一个字节,以确保每个页面都从虚拟内存 (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 日志。

有人要求对这些内容进行准确解释:

日志的第一行是:

[ 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`

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用