RSS udává, kolik paměti má tento proces aktuálně v hlavní paměti (RAM). VSZ udává, kolik virtuální paměti má proces celkem. To zahrnuje všechny typy pamětí, a to jak v RAM, tak swapované. Tato čísla mohou být zkreslená, protože zahrnují také sdílené knihovny a další typy paměti. Můžete mít pět set instancí bash
běží a celková velikost jejich paměťové stopy nebude součtem jejich hodnot RSS nebo VSZ.
Pokud potřebujete získat podrobnější představu o paměťové stopě procesu, máte několik možností. Můžete projít /proc/$PID/map
a vyřadit věci, které se vám nelíbí. Pokud se jedná o sdílené knihovny, výpočet by mohl být složitý v závislosti na vašich potřebách (což si myslím, že si pamatuji).
Pokud vám záleží pouze na velikosti haldy procesu, můžete vždy analyzovat [heap]
záznam v map
soubor. Velikost, kterou jádro alokovalo pro hromadu procesu, může, ale nemusí odrážet přesný počet bajtů, které proces požádal k přidělení. Existují drobné detaily, vnitřnosti jádra a optimalizace, které to mohou shodit. V ideálním světě to bude tolik, kolik váš proces potřebuje, zaokrouhleno nahoru na nejbližší násobek velikosti systémové stránky (getconf PAGESIZE
vám řekne, co to je — na počítačích je to pravděpodobně 4 096 bajtů).
Pokud chcete vidět, kolik paměti proces přidělil , jedním z nejlepších způsobů je vzdát se metrik na straně jádra. Místo toho instrumentujete (de)alokační funkce paměti haldy C knihovny pomocí LD_PRELOAD
mechanismus. Osobně valgrind
mírně zneužívám získat informace o takových věcech. (Všimněte si, že použití instrumentace bude vyžadovat restartování procesu.)
Vezměte prosím na vědomí, protože můžete také srovnávat běhové prostředí, že valgrind
vaše programy budou velmi mírně pomalejší (ale pravděpodobně v rámci vašich tolerancí).
Minimální spustitelný příklad
Aby to dávalo smysl, musíte pochopit základy stránkování:https://stackoverflow.com/questions/18431261/how-does-x86-paging-work a zejména to, že OS může alokovat virtuální paměť pomocí tabulek stránek / vedení knihy vnitřní paměti (virtuální paměť VSZ) předtím, než bude mít skutečně záložní úložiště v RAM nebo na disku (RSS rezidentní paměť).
Nyní, abychom to pozorovali v akci, vytvořte program, který:
- přiděluje více paměti RAM než naše fyzická paměť s
mmap
- na každou stránku zapíše jeden bajt, aby bylo zajištěno, že každá z těchto stránek přejde z pouze virtuální paměti (VSZ) do skutečně používané paměti (RSS)
- kontroluje využití paměti procesem pomocí jedné z metod uvedených na adrese:https://stackoverflow.com/questions/1558402/memory-usage-of-current-process-in-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://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 upstream.
Kompilace a spuštění:
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
kde:
- 0x1000000000 ==64GiB:2x fyzická RAM mého počítače 32GiB
- 0x200000000 ==8GiB:vytiskněte paměť každých 8GiB, takže bychom před zhroucením měli získat 4 výtisky při přibližně 32GiB
echo 1 | sudo tee /proc/sys/vm/overcommit_memory
:vyžadováno pro Linux, aby nám umožnilo volání mmap větší než fyzická RAM:https://stackoverflow.com/questions/2798330/maximum-memory-which-malloc-can-allocate/57687432#57687432
Výstup programu:
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
Stav ukončení:
137
což podle pravidla 128 + číslo signálu znamená, že máme číslo signálu 9
, což je man 7 signal
říká, že je SIGKILL, který je odeslán linuxovým zabijákem nedostatku paměti.
Interpretace výstupu:
- Virtuální paměť VSZ zůstává konstantní na
printf '0x%X\n' 0x40009A4 KiB ~= 64GiB
(ps
hodnoty jsou v KiB) za mmap. - Skutečné využití paměti RSS se líně zvyšuje pouze tehdy, když se dotýkáme stránek. Například:
- na prvním výtisku máme
extra_memory_committed 0
, což znamená, že jsme se ještě nedotkli žádné stránky. RSS je malý1648 KiB
která byla přidělena pro normální spouštění programu, jako je textová oblast, globální hodnoty atd. - při druhém tisku jsme zapsali do
8388608 KiB == 8GiB
v hodnotě stránek. V důsledku toho se RSS zvýšilo přesně o 8 GBP na8390256 KiB == 8388608 KiB + 1648 KiB
- RSS se stále zvyšuje v 8GiB přírůstcích. Poslední tisk ukazuje asi 24 GiB paměti a než bylo možné vytisknout 32 GiB, zabiják OOM tento proces zabil
- na prvním výtisku máme
Viz také:Potřebujete vysvětlení k velikosti rezidentní sady/virtuální velikosti
Protokoly zabijáků OOM
Naše dmesg
příkazy zobrazily protokoly zabijáků OOM.
Jejich přesný výklad byl požádán na adrese:
- https://stackoverflow.com/questions/9199731/understanding-the-linux-oom-killers-logs, ale pojďme se rychle podívat sem.
- https://serverfault.com/questions/548736/how-to-read-oom-killer-syslog-messages
Úplně první řádek protokolu byl:
[ 7283.479087] mongod invoked oom-killer: gfp_mask=0x6200ca(GFP_HIGHUSER_MOVABLE), order=0, oom_score_adj=0
Vidíme tedy, že je zajímavé, že to byl démon MongoDB, který vždy běží v mém notebooku na pozadí, který jako první spustil OOM zabijáka, pravděpodobně když se chudák snažil alokovat nějakou paměť.
OOM zabiják však nemusí nutně zabít toho, kdo ho probudil.
Po vyvolání jádro vytiskne tabulku nebo procesy včetně 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
a dále vidíme naši vlastní malou main.out
vlastně byl zabit při předchozím vyvolání:
[ 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
Tento protokol uvádí score 865
který tento proces měl, pravděpodobně nejvyšší (nejhorší) skóre OOM zabijáka, jak je zmíněno v:Jak se OOM zabiják rozhodne, který proces zabije jako první?
Zajímavé také je, že vše se zjevně stalo tak rychle, že než byla uvolněná paměť zaúčtována, oom
byl znovu probuzen DeadlineMonitor
proces:
[ 7283.481043] DeadlineMonitor invoked oom-killer: gfp_mask=0x6200ca(GFP_HIGHUSER_MOVABLE), order=0, oom_score_adj=0
a tentokrát to zabilo nějaký proces Chromium, což je obvykle normální paměťový prase mých počítačů:
[ 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
Testováno v Ubuntu 19.04, Linux kernel 5.0.0.