Je pense que votre problème initial était que malloc
n'a pas pu allouer la mémoire demandée sur votre système.
La raison pour laquelle cela s'est produit est spécifique à votre système.
Lorsqu'un processus est chargé, de la mémoire lui est allouée jusqu'à une certaine adresse qui est le point d'arrêt du système pour le processus. Au-delà de cette adresse, la mémoire n'est pas cartographiée pour le processus. Ainsi, lorsque le processus "atteint" le point "d'arrêt", il demande plus de mémoire au système et une façon de le faire est via l'appel système sbrk
malloc
le ferait sous le capot, mais dans votre système, pour une raison quelconque, il a échoué.
Il peut y avoir plusieurs raisons à cela par exemple :
1) Je pense que sous Linux, il y a une limite pour la taille maximale de la mémoire. Je pense que c'est ulimit
et peut-être avez-vous touché cela. Vérifiez s'il est défini sur une limite
2) Peut-être que votre système était trop chargé
3) Votre programme fait une mauvaise gestion de la mémoire et vous vous retrouvez avec une mémoire fragmentée donc malloc
ne peut pas obtenir la taille de bloc que vous avez demandée.
4) Votre programme corrompt le malloc
structures de données internes, c'est-à-dire mauvaise utilisation du pointeur
etc
Le tas est généralement aussi grand que la mémoire virtuelle adressable sur votre architecture.
Vous devriez vérifier les limites actuelles de votre système avec le ulimit -a
commande et recherche cette ligne max memory size (kbytes, -m) 3008828
, cette ligne sur mon OpenSuse 11.4 x86_64 avec ~3,5 Go de RAM indique que j'ai environ 3 Go de RAM par processus.
Ensuite, vous pouvez vraiment tester votre système à l'aide de ce programme simple pour vérifier la mémoire maximale utilisable par processus :
#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;
}
Ce programme obtient de la mémoire par incréments de 100 Mo, présente la mémoire actuellement allouée, alloue des 0 dessus, puis libère la mémoire. Lorsque le système ne peut pas donner plus de mémoire, renvoie NULL et affiche la quantité maximale finale utilisable de RAM.
La mise en garde est que votre système commencera à échanger fortement de la mémoire dans les étapes finales. Selon la configuration de votre système, le noyau peut décider de tuer certains processus. J'utilise des incréments de 100 MiB afin qu'il y ait un peu de répit pour certaines applications et le système. Vous devez fermer tout ce que vous ne voulez pas planter.
Cela étant dit. Dans mon système où j'écris ceci, rien ne s'est écrasé. Et le programme ci-dessus rapporte à peine la même chose que ulimit -a
. La différence est qu'il a en fait testé la mémoire et au moyen de memset()
confirmé que la mémoire a été donnée et utilisée.
À titre de comparaison sur une machine virtuelle Ubuntu 10.04x86 avec 256 Mo de RAM et 400 Mo d'échange, le rapport ulimit était memory size (kbytes, -m) unlimited
et mon petit programme a rapporté 524.288.000 octets, ce qui correspond à peu près au RAM et au swap combinés, en excluant le RAM utilisé par d'autres logiciels et le noyau.
Edit :Comme Adam Zalcman l'a écrit, ulimit -m
n'est plus honoré sur les nouveaux noyaux Linux 2.6 et plus, donc je me corrige. Mais ulimit -v
est honoré. Pour des résultats pratiques, vous devez remplacer -m par -v et rechercher virtual memory (kbytes, -v) 4515440
. Il semble par hasard que ma boîte suse ait la valeur -m coïncidant avec ce que mon petit utilitaire a rapporté. N'oubliez pas qu'il s'agit de mémoire virtuelle attribuée par le noyau, si la RAM physique est insuffisante, il faudra de l'espace de swap pour la compenser.
Si vous voulez savoir combien de RAM physique est disponible sans perturber aucun processus ou système, vous pouvez utiliser
long total_available_ram =sysconf(_SC_AVPHYS_PAGES) * sysconf(_SC_PAGESIZE) ;
cela exclura la mémoire cache et la mémoire tampon, de sorte que ce nombre peut être bien inférieur à la mémoire disponible réelle. Les caches du système d'exploitation peuvent être assez volumineux et leur éviction peut donner la mémoire supplémentaire nécessaire, mais cela est géré par le noyau.
La gestion du tas et de la mémoire est une fonctionnalité fournie par votre bibliothèque C (probablement glibc). Il maintient le tas et vous renvoie des morceaux de mémoire chaque fois que vous faites un malloc()
. Il ne connaît pas la limite de taille du tas :chaque fois que vous demandez plus de mémoire que ce qui est disponible sur le tas, il va juste demander plus au noyau (soit en utilisant sbrk()
ou mmap()
).
Par défaut, le noyau vous donnera presque toujours plus de mémoire lorsqu'on vous le demandera. Cela signifie que malloc()
renverra toujours une adresse valide. Ce n'est que lorsque vous faites référence à une page allouée pour la première fois que le noyau se donne la peine de trouver une page pour vous. S'il constate qu'il ne peut pas vous en donner un, il exécute un tueur OOM qui, selon certaines mesures, s'appelle mauvaiseté (qui inclut les tailles de mémoire virtuelle de votre processus et de ses enfants, le niveau agréable, le temps d'exécution global, etc.) sélectionne une victime et lui envoie un SIGTERM
. Cette technique de gestion de la mémoire est appelée overcommit et est utilisée par le noyau lorsque /proc/sys/vm/overcommit_memory
est 0 ou 1. Voir overcommit-accounting dans la documentation du noyau pour plus de détails.
En écrivant 2 dans /proc/sys/vm/overcommit_memory
vous pouvez désactiver le surengagement. Si vous faites cela, le noyau vérifiera s'il a de la mémoire avant de la promettre. Cela se traduira par malloc()
renvoyant NULL si plus de mémoire n'est disponible.
Vous pouvez également définir une limite sur la mémoire virtuelle qu'un processus peut allouer avec setrlimit()
et RLIMIT_AS
ou avec le ulimit -v
commande. Quel que soit le paramètre de surengagement décrit ci-dessus, si le processus tente d'allouer plus de mémoire que la limite, le noyau le refusera et malloc()
renverra NULL. Notez que dans le noyau Linux moderne (y compris toute la série 2.6.x) la limite de la taille résidente (setrlimit()
avec RLIMIT_RSS
ou ulimit -m
commande) est inefficace.
La session ci-dessous a été exécutée sur le noyau 2.6.32 avec 4 Go de RAM et 8 Go d'échange.
$ 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
$
Dans l'exemple ci-dessus, l'échange ou la suppression de MOO ne pourrait jamais se produire, mais cela changerait considérablement si le processus tentait réellement de toucher toute la mémoire allouée.
Pour répondre directement à votre question :à moins que la limite de mémoire virtuelle ne soit explicitement définie avec ulimit -v
commande, il n'y a pas de limite de taille de tas autre que les ressources physiques de la machine ou la limite logique de votre espace d'adressage (pertinent dans les systèmes 32 bits). Votre glibc continuera d'allouer de la mémoire sur le tas et demandera de plus en plus au noyau à mesure que votre tas grandit. Finalement, vous risquez de mal échanger si toute la mémoire physique est épuisée. Une fois l'espace d'échange épuisé, un processus aléatoire sera tué par le tueur OOM du noyau.
Notez cependant que l'allocation de mémoire peut échouer pour bien d'autres raisons que le manque de mémoire libre, la fragmentation ou l'atteinte d'une limite configurée. Le sbrk()
et mmap()
les appels utilisés par l'allocateur de glib ont leurs propres échecs, par ex. le saut de programme a atteint une autre adresse déjà allouée (par exemple, la mémoire partagée ou une page précédemment mappée avec mmap()
) ou le nombre maximal de mappages de mémoire du processus a été dépassé.