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

472

RSS和VSZ在Linux内存管理中是什么?在多线程环境下,这两者如何进行管理和跟踪?

7个回答

706

RSS表示Resident Set Size,用于显示分配给该进程的内存量以及在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 命令查看此信息。

除此之外,还有更多内容可供学习,请参阅以下参考资料:

此外,请参见:


33
我认为 RSS 确实 包括来自动态链接库的内存。如果有 3 个进程都在使用 libxml2.so,那么共享库将计入它们每个进程的 RSS 中,因此它们的 RSS 总和将超过实际使用的内存。 - nfm
5
没错,我修正了我的答案,谢谢你提醒我。 - jmh
我不是很确定。请看一下这篇有关Java虚拟内存使用的答案:https://dev59.com/l3RB5IYBdhLWcg3wpopm#561450。简而言之,VSZ可以包含已分配但未使用的堆空间以及内存映射文件。 - jmh
太好了。只需要添加一些内容。如果你使用malloc(100KB),实际上只使用1KB。rss是1K,vsz是100K,即使这里没有交换。 - keniee van
我的 ps 不允许在 -o 中使用 pss 关键字。 - wick
显示剩余6条评论

61

RSS是物理常驻内存大小(实际占用机器物理内存的空间),而VSZ是虚拟内存大小(已分配地址空间 - 这些地址在进程的内存映射中已分配,但现在并不一定有实际的内存支持它们)。

需要注意的是,在这个普遍使用虚拟机的时代,从机器的角度来看,物理内存可能并不是真正的物理内存。


1
能否提供比缩写更多的信息? - Pithikos

41

最小可运行示例

要理解这个示例,您需要了解分页的基础知识: x86 分页是如何工作的?,尤其是操作系统可以通过页面表/其内部内存管理(VSZ 虚拟内存)分配虚拟内存,而实际上并没有 RAM 或磁盘上的支持存储(RSS 物理内存)。

现在让我们创建一个程序来观察它的实际应用:

  • 使用 mmap 分配比我们的物理内存更多的 RAM
  • 在每个页面上写入一个字节,以确保每个页面从虚拟内存(VSZ)转换为实际使用的内存(RSS)
  • 使用提到的其中一种方法检查进程的内存使用情况: C 中当前进程的内存使用情况

main.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://dev59.com/QHI_5IYBdhLWcg3wBuX3/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://dev59.com/QHI_5IYBdhLWcg3wBuX3 */
        if (bytes_since_last_print > print_interval) {
            bytes_since_last_print -= print_interval;
            printf("extra_memory_committed %lu KiB\n", (i * page_size) / 1024);
            ProcStat_init(&proc_statm);
            /* Check /proc/self/statm */
            printf(
                "/proc/self/statm size resident %lu %lu KiB\n",
                (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 upstream

编译和运行:

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: 是64GiB,是我计算机物理内存32GiB的两倍。
  • 0x200000000 == 8GiB: 每8GiB打印一次内存,因此在大约32GiB处崩溃之前,我们应该会得到4个打印输出。
  • echo 1 | sudo tee /proc/sys/vm/overcommit_memory: Linux需要这个命令才能允许我们进行比物理RAM更大的映射调用:最大可以分配的内存大小

程序输出:

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,而SIGKILLman 7 signal中被解释为由Linux的内存不足杀手发送。

输出解释:

  • 在mmap之后,VSZ虚拟内存保持不变,约为64GiB(ps值以KiB为单位):printf '0x%X\n' 0x40009A4 KiB ~= 64GiB
  • 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 killer杀死了该进程

也可参考: 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 killer,可能是当它试图分配一些内存时。

然而,OOM killer并不一定杀死唤醒它的进程。

调用之后,内核会打印一个包括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

这个日志提到了该进程的得分为865,可能是OOM killer得分中最高(最差)的,如https://unix.stackexchange.com/questions/153585/how-does-the-oom-killer-decide-which-process-to-kill-first所述。
有趣的是,一切似乎发生得非常快,以至于在释放内存之前,DeadlineMonitor 进程再次唤醒了 oom
[ 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 kernel 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中,如果有多个进程使用它们,则不会出现在RSS中。
  • PSS被 jmh提到,并且在“I'm the only process that holds the shared library”和“there are N process holding the shared library, so each one holds memory/N on average”之间采用更加比例化的方法。

13

VSZ - 虚拟集合大小

  • 虚拟集合大小是在进程(程序)初始执行时分配给其使用的内存大小。虚拟集合大小内存仅是该进程用于执行的可用内存量的数字。

RSS - 常驻集合大小(类似于 RAM)

  • 相对于 VSZ(虚拟集合大小),RSS 是当前进程实际使用的内存。这是以千字节为单位的当前进程正在使用的 RAM 的实际数字。

来源


当从stat文件中读取值时,例如8674,那么这个8674是指8674千字节吗?然后Linux man-pages只说明RSS等于进程在内存中持有的“页面数”。那么他们的意思是一页等于一个千字节吗?/proc/[pid]/stathttps://man7.org/linux/man-pages/man5/proc.5.html(24) rss %ld - Daniel Nelson

7

我认为关于RSS与VSZ的问题已经有很多讨论了。从管理员/程序员/用户的角度来看,当我设计/编写应用程序时,我更关心RSZ(常驻内存),因为当你不断地拉取更多的变量(堆积)时,你会看到这个值飙升。尝试编写一个简单的程序,在循环中构建基于malloc的空间分配,并确保在该malloc空间中填充数据。RSS会不断上升。 至于VSZ,它更多的是Linux进行虚拟内存映射的一种方式,是从传统操作系统概念中衍生出来的核心特性之一。VSZ管理由内核的虚拟内存管理完成,有关VSZ的更多信息,请参见Robert Love在内核中的基本task_struct数据结构中描述的mm_struct和vm_struct。


1
你是在提到《Linux内核开发》这本书吗? - benjimin

6
总结@jmh的精彩回答如下:
在#linux中,进程的内存包括:
- 它自己的二进制文件 - 它共享的libs - 它的堆栈
由于分页,它们并不总是完全存在于内存中,只有最有用的,最近使用的部分(页面)才会存在于内存中。其他部分被分页出去(或交换出去),为其他进程腾出空间。
下面的表格取自@jmh的回答,显示了特定进程的Resident和Virtual memory的示例。
+-------------+-------------------------+------------------------+
| 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                  |
+-------------+-------------------------+------------------------+

总结一下:常驻内存是当前实际存在于物理内存中的内容,而虚拟大小则是加载所有组件所需的总体物理内存。当然,数字并不总是准确的,因为库被多个进程共享,并且它们的内存对每个进程都单独计算,即使它们只被加载一次。

0

它们没有被管理,但是可以被测量和可能被限制(请参见getrlimit系统调用,也可在getrlimit(2)中查看)。

RSS表示常驻集大小(虚拟地址空间中位于RAM中的部分)。

您可以使用proc(5)查询进程1234的虚拟地址空间,并通过cat /proc/1234/status查看其状态(包括内存消耗)。


2
虽然这个链接可能回答了问题,但最好在此处包含答案的基本部分并提供参考链接。如果链接页面更改,仅链接的答案可能会失效。- 来自审查 - Maak
我提供了第二个链接。其中一个将保持有效。 - Basile Starynkevitch

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接