web-dev-qa-db-fra.com

Comment vérifier la taille d'un tas pour un processus sous Linux

J'écrivais du code qui n'arrêtait pas de s'écraser. Plus tard, après avoir creusé les dépotoirs, je me suis rendu compte que je dépassais la limite maximale du tas (la vie aurait été plus facile si j'avais ajouté un contrôle sur malloc). Bien que j'aie corrigé ce problème, existe-t-il un moyen d'augmenter la taille de mon tas?

PS: Une question assez similaire ici mais la réponse n’est pas claire pour moi.

10
bashrc

Le tas est généralement aussi grand que la mémoire virtuelle adressable de votre architecture.

Vous devriez vérifier les limites actuelles de votre système avec la commande ulimit -a et rechercher cette ligne au maximum memory size (kbytes, -m) 3008828, cette ligne sur mon OpenSuse 11.4 x86_64 avec ~ 3.5 GiB de la mémoire RAM dit que j'ai environ 3 Go de mémoire RAM par processus.

Ensuite, vous pouvez véritablement tester votre système en utilisant ce programme simple pour vérifier le maximum de mémoire 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, attribue des zéros 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é finale maximale utilisable de RAM.

L'avertissement est que votre système va commencer à fortement échanger 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 Mio afin de laisser un peu de répit à certaines applications et au système. Vous devriez fermer tout ce que vous ne voulez pas écraser.

Cela étant dit. Dans mon système où j'écris ceci, rien ne s'est écrasé. Et le programme ci-dessus indique à peine la même chose que ulimit -a. La différence est qu’elle a effectivement testé la mémoire et a confirmé par memset() que la mémoire était donnée et utilisée.

Pour comparaison sur un Ubuntu 10.04x86 VM avec 256 Mio de RAM et 400 Mo d’échange, le rapport ulimit était memory size (kbytes, -m) unlimited et mon petit programme indiquait 524.288.000 octets, soit à peu près le bélier et l’échange combinés, ram utilisé. par d'autres logiciels et le noyau.

Edit: Comme l’a écrit Adam Zalcman, ulimit -m n’est plus respecté sur les noyaux Linux 2.6 et ultérieurs, donc je reste corrigé. Mais ulimit -v est honoré. Pour obtenir des résultats pratiques, remplacez -m par -v et recherchez virtual memory (kbytes, -v) 4515440. Il semble que ce soit un hasard si ma boîte suse a la valeur -m coïncidant avec ce que mon petit utilitaire a signalé. Vous devez vous rappeler que c'est la mémoire virtuelle assignée par le noyau. Si la RAM physique est insuffisante, il faudra de l'espace d'échange pour compenser.

Si vous voulez savoir combien de RAM physique est disponible sans perturber le moindre processus ou le 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, ce nombre peut donc être beaucoup plus petit que la mémoire disponible réelle. Les caches de système d'exploitation peuvent être très volumineux et leur expulsion peut fournir la mémoire supplémentaire nécessaire, mais le noyau s'en charge.

9
RedComet

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 redonne des morceaux de mémoire chaque fois que vous effectuez une malloc(). Il ne connaît pas la limite de taille de tas: chaque fois que vous demandez plus de mémoire que ce qui est disponible sur le tas, il demande simplement au noyau de lui en demander davantage (en utilisant sbrk() ou mmap()).

Par défaut, le noyau vous donnera presque toujours plus de mémoire quand on vous le demandera. Cela signifie que malloc() renverra toujours une adresse valide. Ce n'est que lorsque vous vous référez pour la première fois à une page allouée que le noyau va réellement chercher une page pour vous. S'il découvre qu'il ne peut pas vous le donner, il lance un tueur OOM qui, selon une certaine mesure, s'appelle badness (qui inclut la taille de la mémoire virtuelle de votre processus et de ses enfants, le niveau de Nice, le temps d'exécution général, etc.) sélectionne une victime. et envoie un SIGTERM. Cette technique de gestion de la mémoire est appelée sur-engagement et est utilisée par le noyau lorsque /proc/sys/vm/overcommit_memory est égal à 0 ou 1. Voir surcommit-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 dispose de mémoire avant de la promettre. Cela entraînera malloc() retournant NULL s'il n'y a plus de mémoire disponible.

Vous pouvez également définir une limite de mémoire virtuelle qu'un processus peut allouer avec setrlimit() et RLIMIT_AS ou avec la commande ulimit -v. Indépendamment du paramètre de surcapacité décrit ci-dessus, si le processus tente d'allouer plus de mémoire que la limite, le noyau la refusera et malloc() renverra NULL. Notez que dans le noyau Linux moderne (y compris l’ensemble de la série 2.6.x), la limite de la taille du résident (setrlimit() avec la commande RLIMIT_RSS ou ulimit -m) est inefficace.

La session ci-dessous a été exécutée sur le noyau 2.6.32 avec une permutation de 4 Go RAM et 8 Go.

$ 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 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 la commande ulimit -v, il n'y a pas de limite de taille de segment 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 à allouer de la mémoire sur le tas et en demandera de plus en plus au noyau à mesure que votre tas grandit. Éventuellement, vous risquez de mal permuter si votre mémoire physique est épuisée. Une fois que l'espace de swap est épuisé, un processus aléatoire sera tué par le tueur de MOO du noyau.

Notez cependant que l’allocation de mémoire peut échouer pour de nombreuses autres raisons que le manque de mémoire disponible, la fragmentation ou l’atteinte d’une limite configurée. Les appels sbrk() et mmap() utilisés par l'allocateur de glib ont leurs propres échecs, par exemple. l'interruption du programme a atteint une autre adresse déjà attribuée (par exemple, une mémoire partagée ou une page mappée précédemment avec mmap()) ou le nombre maximal de mappages en mémoire du processus a été dépassé.

15
Adam Zalcman

Je pense que votre problème initial était que malloc n'a pas pu allouer la mémoire demandée sur votre système. 

Pourquoi est-ce arrivé est spécifique à votre système. 

Lorsqu'un processus est chargé, la mémoire lui est allouée jusqu'à une certaine adresse, qui constitue le point d'arrêt du système pour le processus. Au-delà de cette adresse, la mémoire n'est pas mappée pour le processus. Ainsi, lorsque le processus "atteint" le "point d'arrêt", il demande plus de mémoire au système. Pour cela, vous pouvez utiliser l'appel système sbrk
malloc le ferait sous le capot, mais pour une raison quelconque, votre système échouait. 

Il pourrait y avoir plusieurs raisons à cela, par exemple:
1) Je pense que sous Linux, la taille maximale de la mémoire est limitée. Je pense que c'est ulimit et peut-être que vous frappez cela. Vérifiez si elle est définie sur une limite
2) Peut-être que votre système était trop chargé
3) Votre programme gère mal la mémoire et vous vous retrouvez avec une mémoire fragmentée, de sorte que malloc ne peut pas obtenir la taille de bloc que vous avez demandée.
4) Votre programme corrompt les structures de données internes malloc, c.-à-d. Une mauvaise utilisation du pointeur
etc 

2
Cratylus

J'aimerais ajouter un point aux réponses précédentes.

Les applications ont l'illusion que malloc () renvoie des blocs 'solides'; en réalité, un tampon peut exister dispersé, pulvérisé, sur plusieurs pages de RAM. Le fait crucial ici est le suivant: la mémoire virtuelle d’un processus, contenant son code ou contenant quelque chose comme un grand tableau, must doit être contiguë. Admettons même que le code et les données soient séparés; un grand tableau, char str [taille_univers], doit être contigu.

Maintenant: une seule application peut-elle agrandir le tas arbitrairement, pour allouer un tel tableau?

La réponse pourrait être «oui» s'il n'y avait rien d'autre en cours d'exécution dans la machine. Le tas peut être ridiculement énorme, mais il doit avoir des limites. À un moment donné, les appels à sbrk () (sous Linux, la fonction qui, en résumé, "agrandit" le tas) devraient tomber sur la zone réservée à une autre application.

This link fournit des exemples intéressants et clarifiants, consultez-le. Je n'ai pas trouvé l'info sur Linux.

0
A Koscianski