Maison  >  Article  >  Tutoriel système  >  Comprendre la stratégie d'allocation de mémoire Linux dans un article

Comprendre la stratégie d'allocation de mémoire Linux dans un article

王林
王林avant
2024-02-12 11:57:02833parcourir

À quoi ressemble la distribution mémoire d'un processus Linux ?

Dans le système d'exploitation Linux, l'intérieur de l'espace d'adressage virtuel est divisé en deux parties : l'espace noyau et l'espace utilisateur Les systèmes avec des chiffres différents ont des plages d'espace d'adressage différentes. Par exemple, les systèmes 32 bits et 64 bits les plus courants sont les suivants :

一文读懂 Linux 内存分配策略

Vous pouvez le voir ici :

  • L'espace noyau d'un système 32 bits occupe 1G, qui est en haut, et les 3G restants sont l'espace utilisateur
  •  ;
  • L'espace noyau et l'espace utilisateur des systèmes 64 bits sont tous deux de 128T, occupant respectivement les parties les plus hautes et les plus basses de tout l'espace mémoire, et la partie centrale restante n'est pas définie.

Parlons de la différence entre l'espace noyau et l'espace utilisateur :

  • Lorsque le processus est en mode utilisateur, il ne peut accéder qu'à la mémoire de l'espace utilisateur
  •  ;
  • Ce n'est qu'après être entré dans l'état du noyau que vous pouvez accéder à la mémoire dans l'espace du noyau
  •  ;

Bien que chaque processus possède sa propre mémoire virtuelle indépendante, L'adresse du noyau dans chaque mémoire virtuelle est en fait associée à la même mémoire physique. De cette façon, une fois que le processus est passé à l'état du noyau, il peut facilement accéder à l'espace mémoire du noyau.

一文读懂 Linux 内存分配策略

Ensuite, apprenons-en davantage sur la division de l'espace virtuel. L'espace utilisateur et l'espace noyau sont divisés de différentes manières. Je ne dirai pas grand-chose sur la répartition de l'espace noyau.

Jetons un coup d'œil à la répartition de l'espace utilisateur. En prenant comme exemple un système 32 bits, j'ai dessiné une image pour montrer leur relation :

Vous pouvez voir sur cette image que la mémoire de l'espace utilisateur est divisée en 6 segments de mémoire différents de bas à haut :

一文读懂 Linux 内存分配策略
  • Segment de fichier programme, y compris le code exécutable binaire ;
  • Segment de données initialisé, y compris les constantes statiques ;
  • Segments de données non initialisés, y compris les variables statiques non initialisées ; Le segment de tas, y compris la mémoire allouée dynamiquement, commence à partir de l'adresse basse et grandit vers le haut
  •  ; Les segments de mappage de fichiers, y compris les bibliothèques dynamiques, la mémoire partagée, etc., commencent à partir d'adresses basses et grandissent (en fonction du matériel et de la version du noyau) ;
  • Segment de pile, y compris les variables locales et le contexte d'appel de fonction, etc. La taille de la pile est fixe, généralement 8 Mo. Bien entendu, le système fournit également des paramètres afin que nous puissions personnaliser la taille
  •  ;
  • Parmi ces 6 segments de mémoire, la mémoire des segments de mappage de tas et de fichiers est allouée dynamiquement. Par exemple, en utilisant malloc() ou mmap() de la bibliothèque standard C, vous pouvez allouer dynamiquement de la mémoire dans les segments de mappage de tas et de fichier respectivement.
Comment malloc alloue-t-il la mémoire ?

En fait, malloc() n'est pas un appel système, mais une fonction de la bibliothèque C, utilisée pour allouer dynamiquement de la mémoire. Lorsque malloc demande de la mémoire, il existe deux façons de demander de la mémoire tas à partir du système d'exploitation.

Méthode 1 : allouer de la mémoire à partir du tas via l'appel système brk()

    Méthode 2 : allouez de la mémoire dans la zone de mappage de fichiers via l'appel système mmap() ;
  • L'implémentation de la première méthode est très simple, qui consiste à utiliser la fonction brk() pour déplacer le pointeur "haut du tas" vers une adresse haute afin d'obtenir un nouvel espace mémoire. Comme indiqué ci-dessous :
  • La méthode 2 utilise la méthode "cartographie anonyme privée" dans l'appel système mmap() pour allouer un morceau de mémoire dans la zone de mappage de fichiers, ce qui consiste à "voler" un morceau de mémoire dans la zone de mappage de fichiers. Comme indiqué ci-dessous :
  • 一文读懂 Linux 内存分配策略

    «

    Dans quelles circonstances malloc() alloue-t-il de la mémoire via brk() ? Dans quel scénario la mémoire est-elle allouée via mmap() ?

    malloc() a un seuil défini par défaut dans le code source :

    • Si la mémoire allouée par l'utilisateur est inférieure à 128 Ko, demandez de la mémoire via brk(); Si la mémoire allouée par l'utilisateur est supérieure à 128 Ko, demandez de la mémoire via mmap();
    • Notez que différentes versions de glibc définissent différents seuils.
    • malloc() alloue de la mémoire physique ?

    Non,

    malloc() alloue de la mémoire virtuelle

    .

    Si la mémoire virtuelle allouée n'est pas accédée, la mémoire virtuelle ne sera pas mappée à la mémoire physique, elle n'occupera donc pas la mémoire physique. Seulement lors de l'accès à l'espace d'adressage virtuel alloué, le système d'exploitation recherchera la table des pages et constatera que la page correspondant à la mémoire virtuelle n'est pas dans la mémoire physique, il déclenchera une interruption de défaut de page, puis le système d'exploitation établir un lien entre la mémoire virtuelle et la relation de mappage de la mémoire physique entre.

    Combien de mémoire virtuelle malloc(1) allouera-t-il ?

    Lorsque

    malloc() alloue de la mémoire, il n'alloue pas l'espace mémoire en fonction du nombre d'octets attendu par l'utilisateur, mais pré-alloue un espace plus grand en tant que pool de mémoire.

    La quantité spécifique d'espace qui sera pré-alloué est liée au gestionnaire de mémoire utilisé par malloc Nous utiliserons le gestionnaire de mémoire par défaut de malloc (Ptmalloc2) pour analyser. Ensuite, faisons une expérience et utilisons le code suivant pour voir quelle quantité d'espace mémoire est réellement allouée par le système d'exploitation lors de la demande d'1 octet de mémoire via malloc.

    #include 
    #include 
    
    int main() {
      printf("使用cat /proc/%d/maps查看内存分配\n",getpid());
      
      //申请1字节的内存
      void *addr = malloc(1);
      printf("此1字节的内存起始地址:%x\n", addr);
      printf("使用cat /proc/%d/maps查看内存分配\n",getpid());
     
      //将程序阻塞,当输入任意字符时才往下执行
      getchar();
    
      //释放内存
      free(addr);
      printf("释放了1字节的内存,但heap堆并不会释放\n");
      
      getchar();
      return 0;
    }
    

    Exécutez le code (

    Je m'explique à l'avance, la version de la bibliothèque glibc que j'utilise est la 2.17

    ) :

    Nous pouvons visualiser la distribution de mémoire du processus via le fichier /proc//maps. Je filtre la plage d'adresses mémoire dans le fichier de cartes par cette adresse de début de mémoire de 1 octet.

    [root@xiaolin ~]# cat /proc/3191/maps | grep d730
    00d73000-00d94000 rw-p 00000000 00:00 0                                  [heap]
    

    La mémoire allouée dans cet exemple est inférieure à 128 Ko, donc la mémoire est appliquée à l'espace du tas via l'appel système brk(), vous pouvez donc voir la marque [heap] à l'extrême droite. 一文读懂 Linux 内存分配策略

    Vous pouvez voir que la plage d'adresses mémoire de l'espace du tas est 00d73000-00d94000 et que la taille de cette plage est de 132 Ko, ce qui signifie que

    malloc(1) pré-alloue en fait 132 Ko de mémoire

    .

    Certains étudiants ont peut-être remarqué que l'adresse de départ de la mémoire imprimée dans le programme est d73010, alors que le fichier maps montre que l'adresse de départ de l'espace mémoire du tas est d73000. Pourquoi y a-t-il un 0x10 supplémentaire (16 octets) ? Laissons cette question de côté pour l’instant et parlons-en plus tard. #free La mémoire libérée sera-t-elle restituée au système d'exploitation ?

    Exécutons le processus ci-dessus pour voir si la mémoire du tas est toujours là après que la mémoire soit libérée via la fonction free() ?

    Comme vous pouvez le voir sur l'image ci-dessous, après avoir libéré la mémoire, la mémoire tas existe toujours et n'a pas été restituée au système d'exploitation.

    一文读懂 Linux 内存分配策略En effet, au lieu de libérer ce 1 octet sur le système d'exploitation, il est préférable de le mettre en cache et de le mettre dans le pool de mémoire de malloc. Lorsque le processus demande à nouveau 1 octet de mémoire, il peut être directement réutilisé, ce qui est possible. est beaucoup plus rapide.

    Bien sûr, à la fin du processus, le système d'exploitation récupérera toutes les ressources du processus. 一文读懂 Linux 内存分配策略

    La mémoire du tas existe toujours après que la mémoire libre mentionnée ci-dessus soit destinée à la mémoire appliquée par malloc via la méthode brk().

    Si malloc demande de la mémoire via mmap, elle sera renvoyée au système d'exploitation après avoir libéré la mémoire.

    Faisons une expérience pour vérifier que nous demandons 128 Ko de mémoire via malloc, afin que malloc alloue de la mémoire via mmap.

    #include 
    #include 
    
    int main() {
      //申请1字节的内存
      void *addr = malloc(128*1024);
      printf("此128KB字节的内存起始地址:%x\n", addr);
      printf("使用cat /proc/%d/maps查看内存分配\n",getpid());
    
      //将程序阻塞,当输入任意字符时才往下执行
      getchar();
    
      //释放内存
      free(addr);
      printf("释放了128KB字节的内存,内存也归还给了操作系统\n");
    
      getchar();
      return 0;
    }
    

    Code d'exécution :

    一文读懂 Linux 内存分配策略

    En regardant la distribution de la mémoire du processus, vous pouvez constater qu'il n'y a pas de marque [head] à l'extrême droite, indiquant que la mémoire anonyme est allouée à partir de la zone de mappage de fichiers via mmap via un mappage anonyme.

    一文读懂 Linux 内存分配策略

    Alors libérons cette mémoire et voyons :

    一文读懂 Linux 内存分配策略

    Vérifiez à nouveau l'adresse de départ de la mémoire de 128 Ko et vous constaterez qu'elle n'existe plus, indiquant qu'elle a été renvoyée au système d'exploitation.

    一文读懂 Linux 内存分配策略

    Concernant la question "La mémoire demandée par malloc et libérée gratuitement sera-t-elle restituée au système d'exploitation ?", on peut faire un résumé :

    • La mémoire appliquée par malloc via brk(), lorsque free libère la mémoire, ne restituera pas la mémoire au système d'exploitation, mais sera mise en cache dans le pool de mémoire de malloc pour la prochaine utilisation  ; Lorsque free libère la mémoire allouée par malloc via
    • mmap()
    • , restituera la mémoire au système d'exploitation, et la mémoire est véritablement libérée.
    Pourquoi tous n'utilisent-ils pas mmap pour allouer de la mémoire ?

    Parce que la demande de mémoire à partir du système d'exploitation nécessite un appel système. Pour exécuter un appel système, vous devez entrer dans l'état du noyau, puis revenir à l'état utilisateur. Le passage à l'état d'exécution prendra beaucoup de temps.

    Ainsi, l'opération de demande de mémoire doit éviter les appels système fréquents. Si mmap est utilisé pour allouer de la mémoire, cela signifie que les appels système doivent être exécutés à chaque fois.

    De plus, comme la mémoire allouée par mmap sera restituée au système d'exploitation à chaque fois qu'elle est libérée, l'adresse virtuelle allouée par mmap est donc dans un état de défaut de page à chaque fois, puis lors du premier accès à l'adresse virtuelle. temps, il sera déclenché une interruption de défaut de page.

    En d'autres termes,

    si la mémoire est fréquemment allouée via mmap, non seulement l'état d'exécution sera changé à chaque fois, mais une interruption de défaut de page se produira également (après le premier accès à l'adresse virtuelle), ce qui entraînera une utilisation importante du processeur. consommation

    . Afin d'améliorer ces deux problèmes, lorsque malloc demande de la mémoire dans l'espace du tas via l'appel système brk(), puisque l'espace du tas est continu, il pré-alloue directement une mémoire plus grande en tant que pool de mémoire lorsque la mémoire est libérée. , il est mis en cache dans le pool de mémoire.

    Lorsque vous demanderez de la mémoire la prochaine fois, retirez simplement le bloc de mémoire correspondant directement du pool de mémoire, et la relation de mappage entre l'adresse virtuelle et l'adresse physique de ce bloc de mémoire peut toujours exister. Cela réduit non seulement le nombre de. appels système, cela réduit également le nombre d'interruptions de faute de page, ce qui réduira considérablement la consommation du processeur

    .

    Puisque brk est tellement génial, pourquoi ne pas utiliser brk pour toutes les allocations ?

    Nous avons mentionné plus tôt que la mémoire allouée depuis l'espace du tas via brk ne sera pas restituée au système d'exploitation, considérons donc un tel scénario.

    Si nous demandons successivement trois morceaux de mémoire 10k, 20k et 30k, si 10k et 20k sont libérés et deviennent de l'espace mémoire libre, si la mémoire appliquée pour la prochaine fois est inférieure à 30k, alors cet espace mémoire libre peut être réutilisé .

    Mais si la mémoire demandée la prochaine fois est supérieure à 30 Ko, il n'y a pas d'espace mémoire libre disponible et vous devez vous adresser au système d'exploitation, et la mémoire réelle utilisée continuera d'augmenter. 一文读懂 Linux 内存分配策略

    Par conséquent, à mesure que le système effectue des mallocs et libère fréquemment, en particulier pour les petits blocs de mémoire, de plus en plus de fragments inutilisables seront générés dans le tas, entraînant des « fuites de mémoire ». Ce phénomène de « fuite » ne peut pas être détecté avec valgrind.

    Ainsi, dans l'implémentation de malloc, les différences, avantages et inconvénients dans le comportement de brk et mmap sont pleinement pris en compte, et un gros bloc de mémoire (128 Ko) est alloué par défaut avant que mmap ne soit utilisé pour allouer de l'espace mémoire.

    La fonction free() ne transmet qu'une adresse mémoire. Pourquoi peut-elle savoir combien de mémoire libérer ?

    Rappelez-vous, j'ai mentionné plus tôt que l'adresse de départ de la mémoire renvoyée en mode utilisateur par malloc est 16 octets de plus que l'adresse de départ de l'espace de tas du processus ?

    Les 16 octets supplémentaires stockent les informations de description du bloc mémoire, telles que la taille du bloc mémoire.

    一文读懂 Linux 内存分配策略

    De cette façon, lorsque la fonction free() est exécutée, free décalera l'adresse mémoire entrante de 16 octets vers la gauche, puis analysera la taille du bloc mémoire actuel à partir de ces 16 octets, et saura naturellement quelle est sa taille doit être libéré de la mémoire.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer