需要有关驻留集大小/虚拟大小的说明

需要有关驻留集大小/虚拟大小的说明

我发现这pidstat将是一个监控流程的好工具。我想计算特定进程的平均内存使用情况。这是一些示例输出:

02:34:36 PM       PID  minflt/s  majflt/s     VSZ    RSS   %MEM  Command
02:34:37 PM      7276      2.00      0.00  349212 210176   7.14  scalpel

(这是输出的一部分pidstat -r -p 7276。)

我应该使用驻留集大小 (RSS) 还是虚拟大小 (VSZ) 信息来计算平均内存消耗?我在维基百科和论坛上阅读了一些内容,但我不确定是否完全理解其中的差异。另外,似乎没有一个是可靠的。那么,如何监控进程以获取其内存使用情况呢?

关于这个问题的任何帮助都会很有用。

答案1

RSS 是该进程当前在主内存 (RAM) 中拥有的内存量。 VSZ 是进程总共有多少虚拟内存。这包括所有类型的内存,包括 RAM 中的内存和换出的内存。这些数字可能会出现偏差,因为它们还包括共享库和其他类型的内存。您可以bash运行 500 个实例,并且它们的内存占用的总大小不会是它们的 RSS 或 VSZ 值的总和。

如果您需要更详细地了解进程的内存占用情况,您有一些选择。你可以浏览/proc/$PID/map并剔除你不喜欢的东西。如果它是共享库,根据您的需要,计算可能会变得复杂(我想我记得)。

如果您只关心进程的堆大小,则始终可以只解析文件[heap]中的条目map。内核为进程堆分配的大小可能反映也可能不反映进程拥有的确切字节数待分配。有一些微小的细节、内核内部结构和优化可以解决这个问题。在理想情况下,它会是您的进程需要的大小,四舍五入到最接近的系统页面大小的倍数(getconf PAGESIZE会告诉您它是什么 - 在 PC 上,它可能是 4,096 字节)。

如果你想查看一个进程有多少内存已分配,最好的方法之一是放弃内核端指标。相反,您可以使用该机制来检测 C 库的堆内存(释放)分配函数LD_PRELOAD。就我个人而言,我有点滥用valgrind获取有关此类事情的信息。 (请注意,应用检测将需要重新启动该过程。)

请注意,由于您可能还对运行时进行基准测试,这valgrind将使您的程序稍微慢一些(但可能在您的容忍范围内)。

答案2

最小可运行示例

为了使这一点有意义,您必须了解分页的基础知识:https://stackoverflow.com/questions/18431261/how-does-x86-paging-work特别是,操作系统可以在 RAM 或磁盘上实际拥有后备存储(RSS 驻留内存)之前通过页表/其内部内存簿保存(VSZ 虚拟内存)分配虚拟内存。

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

主程序

#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

在哪里:

程序输出:

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+信号数规则意味着我们得到了信号编号9man 7 signal信号杀死,由Linux发送内存不足杀手

输出解释:

  • mmap 之后,VSZ 虚拟内存保持恒定printf '0x%X\n' 0x40009A4 KiB ~= 64GiB(值以 KiB 为单位)。ps
  • 仅当我们触摸页面时,RSS“实际内存使用量”才会缓慢增加。例如:
    • 在第一次打印时,我们有extra_memory_committed 0,这意味着我们还没有触及任何页面。 RSS 是一个小块1648 KiB,已分配用于正常程序启动,如文本区域、全局变量等。
    • 在第二次打印时,我们已经写了8388608 KiB == 8GiB很多页。结果,RSS 正好增加了 8GIB8390256 KiB == 8388608 KiB + 1648 KiB
    • RSS 继续以 8GiB 的增量增长。最后一次打印显示了大约 24 GiB 的内存,在打印 32 GiB 之前,OOM 杀手杀死了该进程

也可以看看:需要有关驻留集大小/虚拟大小的说明

OOM 杀手日志

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

已在以下位置询问了这些内容的确切解释:

日志的第一行是:

[ 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 杀手分数,如下所述:OOM 杀手如何决定首先杀死哪个进程?

同样有趣的是,一切显然发生得如此之快,以至于在释放的内存被计算之前,进程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 中测试。

相关内容