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


它们不是管理的,而是度量的,并且可能是有限的(参见getrlimit系统调用,也在getrlimit(2)上)。

RSS意味着常驻集大小(位于RAM中的虚拟地址空间的一部分)。

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


RSS是常驻设置大小(物理常驻内存——这是当前占用机器物理内存中的空间),VSZ是虚拟内存大小(分配的地址空间——它在进程的内存映射中分配了地址,但现在它背后不一定有任何实际内存)。

请注意,在虚拟机非常普遍的今天,从机器的角度来看,物理内存可能并不是真正的物理内存。


RSS是常驻设置大小,用于显示分配给该进程的内存大小和内存大小。它不包括被换出的内存。它确实包括来自共享库的内存,只要来自这些库的页面实际上在内存中。它确实包括所有堆栈和堆内存。

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与进程中所有其他线程相同。使用ps或top在linux/unix中查看此信息。

还有比这更多的方法,要了解更多,请查看以下参考资料:

http://manpages.ubuntu.com/manpages/en/man1/ps.1.html https://web.archive.org/web/20120520221529/http://emilics.com/blog/article/mconsumption.html

还看到:

一种方法来确定进程的“真实”内存使用,即私有脏RSS?


I think much has already been said, about RSS vs VSZ. From an administrator/programmer/user perspective, when I design/code applications I am more concerned about the RSZ, (Resident memory), as and when you keep pulling more and more variables (heaped) you will see this value shooting up. Try a simple program to build malloc based space allocation in loop, and make sure you fill data in that malloc'd space. RSS keeps moving up. As far as VSZ is concerned, it's more of virtual memory mapping that linux does, and one of its core features derived out of conventional operating system concepts. The VSZ management is done by Virtual memory management of the kernel, for more info on VSZ, see Robert Love's description on mm_struct and vm_struct, which are part of basic task_struct data structure in kernel.


最小可运行示例

为此,您必须了解分页的基础知识:x86分页是如何工作的?特别是操作系统可以通过页表/其内部内存簿记(VSZ虚拟内存)分配虚拟内存,而实际上它在RAM或磁盘上有备份存储(RSS常驻内存)。

现在,为了观察这个过程,让我们创建一个程序:

使用mmap分配比物理内存更多的RAM 在每个页面上写入一个字节,以确保每个页面都从虚拟内存(VSZ)转换为实际使用的内存(RSS) 在C中使用当前进程的内存使用情况

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\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上游。

编译并运行:

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: 2x我的计算机的物理RAM为32GiB 0x200000000 == 8GiB:每8GiB打印一次内存,所以我们应该在崩溃前得到4次打印,大约32GiB 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号信号,7号信号表示是SIGKILL,它是由Linux内存不足杀手发送的。

输出的解释:

VSZ virtual memory remains constant at printf '0x%X\n' 0x40009A4 KiB ~= 64GiB (ps values are in KiB) after the mmap. RSS "real memory usage" increases lazily only as we touch the pages. For example: on the first print, we have extra_memory_committed 0, which means we haven't yet touched any pages. RSS is a small 1648 KiB which has been allocated for normal program startup like text area, globals, etc. on the second print, we have written to 8388608 KiB == 8GiB worth of pages. As a result, RSS increased by exactly 8GIB to 8390256 KiB == 8388608 KiB + 1648 KiB RSS continues to increase in 8GiB increments. The last print shows about 24 GiB of memory, and before 32 GiB could be printed, the OOM killer killed the process

参见:https://unix.stackexchange.com/questions/35129/need-explanation-on-resident-set-size-virtual-size

OOM杀手日志

我们的dmesg命令已经显示了OOM杀手日志。

对这些问题的确切解释是:

了解Linux房间杀手的日志,但让我们在这里快速浏览一下。 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

再往前走,我们看到我们自己的小主河道。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,可能是在https://unix.stackexchange.com/questions/153585/how-does-the-oom-killer-decide-which-process-to-kill-first中提到的最高(最差)OOM杀手得分

同样有趣的是,一切都发生得如此之快,以至于在释放的内存被计算之前,oom再次被DeadlineMonitor进程唤醒:

[ 7283.481043] DeadlineMonitor invoked oom-killer: gfp_mask=0x6200ca(GFP_HIGHUSER_MOVABLE), order=0, oom_score_adj=0

这一次,它杀死了一些铬进程,这通常是我的电脑正常的内存占用:

[ 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上。

一些有趣的引用:

The first of these lines shows the same information as is displayed for the mapping in /proc/PID/maps. Following lines show the size of the mapping (size); the size of each page allocated when backing a VMA (KernelPageSize), which is usually the same as the size in the page table entries; the page size used by the MMU when backing a VMA (in most cases, the same as KernelPageSize); the amount of the mapping that is currently resident in RAM (RSS); the process' proportional share of this mapping (PSS); and the number of clean and dirty shared and private pages in the mapping. The "proportional set size" (PSS) of a process is the count of pages it has in memory, where each page is divided by the number of processes sharing it. So if a process has 1000 pages all to itself, and 1000 shared with one other process, its PSS will be 1500. Note that even a page which is part of a MAP_SHARED mapping, but has only a single pte mapped, i.e. is currently used by only one process, is accounted as private and not as shared.

所以我们可以猜测更多的事情:

单个进程使用的共享库出现在RSS中,如果不止一个进程拥有共享库,则没有 PSS是jmh提到的,它在“我是唯一持有共享库的进程”和“有N个持有共享库的进程,所以每个进程平均持有内存/N”之间有一个更成比例的方法。


虚拟集大小

虚拟集大小是在初始执行期间分配给进程(程序)的内存大小。虚拟集大小内存只是一个进程可用于执行的内存数量。

RSS -常驻设置大小(有点RAM)

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


总结@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                  |
+-------------+-------------------------+------------------------+

总而言之:常驻内存是当前物理内存中实际存在的内存,而虚拟大小是加载所有组件所需的全部物理内存。

当然,这些数字不能相加,因为库是在多个进程之间共享的,并且它们的内存是为每个进程单独计算的,即使它们只加载一次。