Минимальный исполняемый пример
Чтобы это имело смысл, вы должны понимать основы подкачки: https://stackoverflow.com/questions/18431261/how-does-x86-paging-work и, в частности, что ОС может выделять виртуальную память через таблицы страниц / ведение внутренней памяти (виртуальная память VSZ) до того, как у нее фактически будет резервное хранилище в ОЗУ или на диске (резидентная память RSS).
Теперь, чтобы увидеть это в действии, давайте создадим программу, которая:
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://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 + номер сигнала означает, что мы получили номер сигнала 9
, который man 7 signal
говорит , что это SIGKILL , который посылает убийца нехватки памяти Linux .
Выходная интерпретация:
- Виртуальная память VSZ остается постоянной
printf '0x%X\n' 0x40009A4 KiB ~= 64GiB
( ps
значения в КиБ) после mmap.
- RSS «реальное использование памяти» увеличивается лениво только при касании страниц. Например:
- на первом отпечатке мы имеем
extra_memory_committed 0
, что означает, что мы еще не коснулись ни одной страницы. RSS - это небольшой файл, 1648 KiB
который был выделен для обычного запуска программы, такого как текстовая область, глобальные переменные и т. Д.
- на втором отпечатке мы написали к
8388608 KiB == 8GiB
стоимости страниц. В результате RSS увеличился ровно на 8GIB до8390256 KiB == 8388608 KiB + 1648 KiB
- RSS продолжает расти с шагом 8 ГБ. Последний отпечаток показывает около 24 ГиБ памяти, и до того, как можно было распечатать 32 ГиБ, убийца OOM убил процесс
См. Также: Требуется объяснение размера резидентного набора / виртуального размера
Журналы убийцы ООМ
Наши dmesg
команды показали журналы убийцы OOM.
Точная интерпретация этих вопросов была задана по адресу:
Самая первая строка журнала была:
[ 7283.479087] mongod invoked oom-killer: gfp_mask=0x6200ca(GFP_HIGHUSER_MOVABLE), order=0, oom_score_adj=0
Итак, мы видим, что интересно, что это был демон MongoDB, который всегда запускался в моем ноутбуке на фоне, который первым вызвал убийцу 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.