Myslím, že váš původní problém byl malloc
se nepodařilo alokovat požadovanou paměť ve vašem systému.
Proč k tomu došlo, je specifické pro váš systém.
Když je proces načten, je mu přidělena paměť až do určité adresy, která je bodem přerušení systému pro proces. Za touto adresou není paměť pro proces namapována. Takže když proces „zasáhne“ bod „zlomu“, vyžádá si od systému více paměti a jedním ze způsobů, jak toho dosáhnout, je systémové volání sbrk
malloc
udělal by to pod kapotou, ale ve vašem systému to z nějakého důvodu selhalo.
Důvodů může být mnoho, například:
1) Myslím, že v Linuxu existuje limit pro maximální velikost paměti. Myslím, že je to ulimit
a možná jsi to trefil. Zkontrolujte, zda je nastaven na limit
2) Možná byl váš systém příliš zatížen
3) Váš program špatně spravuje paměť a vy skončíte s fragmentovanou pamětí, takže malloc
nelze získat požadovanou velikost bloku.
4) Váš program poškodí malloc
interní datové struktury, tj. špatné použití ukazatele
atd.
Halda je obvykle tak velká jako adresovatelná virtuální paměť na vaší architektuře.
Měli byste zkontrolovat aktuální limity vašeho systému pomocí ulimit -a
příkaz a vyhledejte tento řádek max memory size (kbytes, -m) 3008828
, tento řádek na mém OpenSuse 11.4 x86_64 s ~3,5 GiB paměti RAM říká, že mám zhruba 3 GB paměti RAM na proces.
Pak můžete svůj systém skutečně otestovat pomocí tohoto jednoduchého programu pro kontrolu maximální využitelné paměti na proces:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc,char* argv[]){
size_t oneHundredMiB=100*1048576;
size_t maxMemMiB=0;
void *memPointer = NULL;
do{
if(memPointer != NULL){
printf("Max Tested Memory = %zi\n",maxMemMiB);
memset(memPointer,0,maxMemMiB);
free(memPointer);
}
maxMemMiB+=oneHundredMiB;
memPointer=malloc(maxMemMiB);
}while(memPointer != NULL);
printf("Max Usable Memory aprox = %zi\n",maxMemMiB-oneHundredMiB);
return 0;
}
Tento program získá paměť v krocích po 100 MiB, zobrazí aktuálně přidělenou paměť, přidělí jí nuly a poté paměť uvolní. Když systém nemůže poskytnout více paměti, vrátí hodnotu NULL a zobrazí konečné maximální použitelné množství paměti RAM.
Varování spočívá v tom, že váš systém začne v závěrečných fázích intenzivně swapovat paměť. V závislosti na konfiguraci vašeho systému se může jádro rozhodnout některé procesy zabít. Používám přírůstky 100 MiB, takže pro některé aplikace a systém existuje prostor pro dýchání. Měli byste zavřít vše, co nechcete, aby se zhroutilo.
To bylo řečeno. V mém systému, kde to píšu, nic nespadlo. A výše uvedený program hlásí sotva totéž jako ulimit -a
. Rozdíl je v tom, že ve skutečnosti testoval paměť a pomocí memset()
potvrdil, že paměť byla dána a použita.
Pro srovnání na Ubuntu 10.04x86 VM s 256 MiB paměti RAM a 400 MiB swapu byla zpráva ulimit memory size (kbytes, -m) unlimited
a můj malý program hlásil 524 288 000 bajtů, což je zhruba kombinace paměti RAM a swapu, bez ohledu na paměť RAM používanou jiným softwarem a jádrem.
Edit:Jak napsal Adam Zalcman, ulimit -m
již není ctěn na novějších linuxových jádrech 2.6 a vyšší, takže jsem opraven. Ale ulimit -v
je poctěn. Pro praktické výsledky byste měli nahradit -m za -v a hledat virtual memory (kbytes, -v) 4515440
. Zdá se, že je pouhá náhoda, že můj suse box měl hodnotu -m shodující se s tím, co hlásil můj malý nástroj. Měli byste si pamatovat, že se jedná o virtuální paměť přiřazenou jádrem, pokud fyzická paměť RAM nestačí, bude to vyžadovat odkládací prostor, aby se to nahradilo.
Pokud chcete vědět, kolik fyzické paměti RAM je k dispozici, aniž byste narušili jakýkoli proces nebo systém, můžete použít
long total_available_ram =sysconf(_SC_AVPHYS_PAGES) * sysconf(_SC_PAGESIZE) ;
tím se vyloučí mezipaměť a vyrovnávací paměť, takže toto číslo může být mnohem menší než skutečná dostupná paměť. Mezipaměti OS mohou být klidně velké a jejich vyřazení může poskytnout potřebnou paměť navíc, ale to řeší jádro.
Správa haldy a paměti je funkce poskytovaná vaší knihovnou C (pravděpodobně glibc). Udržuje hromadu a vrací vám kusy paměti pokaždé, když provedete malloc()
. Nezná limit velikosti haldy:pokaždé, když požadujete více paměti, než kolik je na haldě k dispozici, prostě odejde a požádá jádro o více (buď pomocí sbrk()
nebo mmap()
).
Ve výchozím nastavení vám jádro na požádání téměř vždy poskytne více paměti. To znamená, že malloc()
vždy vrátí platnou adresu. Pouze když se poprvé odkážete na přidělenou stránku, jádro se skutečně obtěžuje najít vám stránku. Pokud zjistí, že vám žádný nemůže předat, spustí OOM zabijáka, který se podle jisté míry nazývá špatnost (což zahrnuje velikost virtuální paměti vašeho procesu a jeho potomků, příjemnou úroveň, celkovou dobu běhu atd.) vybere oběť a odešle jí SIGTERM
. Tato technika správy paměti se nazývá overcommit a používá ji jádro, když /proc/sys/vm/overcommit_memory
je 0 nebo 1. Podrobnosti naleznete v dokumentaci kernelu o overcommit-accounting.
Zapsáním 2 do /proc/sys/vm/overcommit_memory
můžete deaktivovat overcommit. Pokud to uděláte, jádro před slibem skutečně zkontroluje, zda má paměť. Výsledkem bude malloc()
vrací NULL, pokud není k dispozici více paměti.
Pomocí setrlimit()
můžete také nastavit limit virtuální paměti, kterou může proces přidělit a RLIMIT_AS
nebo pomocí ulimit -v
příkaz. Bez ohledu na výše popsané nastavení overcommit, pokud se proces pokusí alokovat více paměti, než je limit, jádro to odmítne a malloc()
vrátí NULL. Všimněte si, že v moderním linuxovém jádře (včetně celé řady 2.6.x) je limit na rezidentní velikost (setrlimit()
s RLIMIT_RSS
nebo ulimit -m
příkaz) je neúčinný.
Níže uvedená relace byla spuštěna na jádře 2.6.32 se 4 GB RAM a 8 GB swap.
$ cat bigmem.c
#include <stdlib.h>
#include <stdio.h>
int main() {
int i = 0;
for (; i < 13*1024; i++) {
void* p = malloc(1024*1024);
if (p == NULL) {
fprintf(stderr, "malloc() returned NULL on %dth request\n", i);
return 1;
}
}
printf("Allocated it all\n");
return 0;
}
$ cc -o bigmem bigmem.c
$ cat /proc/sys/vm/overcommit_memory
0
$ ./bigmem
Allocated it all
$ sudo bash -c "echo 2 > /proc/sys/vm/overcommit_memory"
$ cat /proc/sys/vm/overcommit_memory
2
$ ./bigmem
malloc() returned NULL on 8519th request
$ sudo bash -c "echo 0 > /proc/sys/vm/overcommit_memory"
$ cat /proc/sys/vm/overcommit_memory
0
$ ./bigmem
Allocated it all
$ ulimit -v $(( 1024*1024 ))
$ ./bigmem
malloc() returned NULL on 1026th request
$
Ve výše uvedeném příkladu by k odkládání nebo zabíjení OOM nikdy nemohlo dojít, ale to by se výrazně změnilo, pokud by se proces skutečně pokusil dotknout se veškeré přidělené paměti.
Chcete-li odpovědět přímo na vaši otázku:pokud nemáte limit virtuální paměti explicitně nastaven na ulimit -v
neexistuje žádný limit velikosti haldy kromě fyzických zdrojů stroje nebo logického limitu vašeho adresního prostoru (relevantní v 32bitových systémech). Váš glibc bude nadále přidělovat paměť na hromadě a bude od jádra vyžadovat stále více a více, jak bude vaše halda růst. Pokud je vyčerpána veškerá fyzická paměť, nakonec můžete swapovat špatně. Jakmile je odkládací prostor vyčerpán, náhodný proces bude zabit jaderným OOM zabijákem.
Pamatujte však, že alokace paměti může selhat z mnoha dalších důvodů, než je nedostatek volné paměti, fragmentace nebo dosažení nakonfigurovaného limitu. sbrk()
a mmap()
volání používaná alokátorem glib mají svá vlastní selhání, např. přerušení programu dosáhlo jiné, již přidělené adresy (např. sdílené paměti nebo stránky dříve namapované pomocí mmap()
) nebo byl překročen maximální počet mapování paměti procesu.