J'ai vu plusieurs publications sur le Web de personnes se plaignant apparemment d'un VPS hébergé tuant de manière inattendue des processus parce qu'ils utilisaient trop de RAM.
Comment est-ce possible? Je pensais que tous les systèmes d'exploitation modernes fournissent une «RAM infinie» en utilisant simplement un échange de disque pour tout ce qui va sur la RAM physique. Est-ce correct?
Que peut-il se passer si un processus est "tué en raison d'une faible RAM"?
Réponses:
On dit parfois que Linux par défaut ne refuse jamais les demandes de mémoire supplémentaire à partir du code d'application - par exemple
malloc()
. 1 Ce n'est pas vrai en fait; la valeur par défaut utilise une heuristique selon laquelleDe
[linux_src]/Documentation/vm/overcommit-accounting
(toutes les citations proviennent de l'arbre 3.11). Ce qui compte exactement comme une «allocation sérieusement sauvage» n'est pas rendu explicite, il nous faudrait donc passer par la source pour déterminer les détails. Nous pourrions également utiliser la méthode expérimentale de la note de bas de page 2 (ci-dessous) pour essayer d'obtenir une certaine réflexion de l'heuristique - sur cette base, mon observation empirique initiale est que dans des circonstances idéales (== le système est inactif), si vous ne le faites pas '' Si vous avez un swap, vous serez autorisé à allouer environ la moitié de votre RAM, et si vous avez un swap, vous obtiendrez environ la moitié de votre RAM plus la totalité de votre swap. C'est plus ou moins par processus (mais notez que cette limite est dynamique et susceptible de changer en raison de l'état, voir quelques observations dans la note 5).La moitié de votre RAM plus swap est explicitement la valeur par défaut pour le champ "CommitLimit" dans
/proc/meminfo
. Voici ce que cela signifie - et notez que cela n'a rien à voir avec la limite qui vient d'être discutée (à partir de[src]/Documentation/filesystems/proc.txt
):Le document de comptabilité de surengagement précédemment cité indique que la valeur par défaut
vm.overcommit_ratio
est 50. Donc, si voussysctl vm.overcommit_memory=2
, vous pouvez ensuite ajuster vm.covercommit_ratio (avecsysctl
) et voir les conséquences. 3 Le mode par défaut, lorsqu'ilCommitLimit
n'est pas appliqué et que «les surcharges évidentes d'espace d'adressage sont refusées», est lorsquevm.overcommit_memory=0
.Bien que la stratégie par défaut ait une limite heuristique par processus empêchant «l'allocation sérieusement sauvage», elle laisse le système dans son ensemble libre de devenir sérieusement sauvage, en termes d'allocation. 4 Cela signifie qu'à un moment donné, il peut manquer de mémoire et devoir déclarer faillite à certains processus via le tueur OOM .
Qu'est-ce que le tueur OOM tue? Pas nécessairement le processus qui demandait de la mémoire quand il n'y en avait pas, car ce n'est pas nécessairement le processus vraiment coupable, et plus important encore, pas nécessairement celui qui éliminera le plus rapidement le système du problème dans lequel il se trouve.
Ceci est cité à partir d' ici qui cite probablement une source 2.6.x:
Ce qui semble être une justification décente. Cependant, sans devenir médico-légal, le n ° 5 (qui est redondant du n ° 1) semble être une mise en œuvre difficile à vendre, et le n ° 3 est redondant du n ° 2. Il pourrait donc être judicieux de considérer cela comme réduit aux # 2/3 et # 4.
J'ai parcouru une source récente (3.11) et j'ai remarqué que ce commentaire avait changé entre-temps:
Ceci est un peu plus explicitement à propos de # 2: "Le but est de [tuer] la tâche consommant le plus de mémoire pour éviter les échecs OOM ultérieurs", et par implication # 4 ( "nous voulons tuer le nombre minimum de processus ( un ) ) .
Si vous voulez voir le tueur OOM en action, voir la note de bas de page 5.
1 Une illusion dont Gilles m'a heureusement débarrassé, voir commentaires.
2 Voici un bit simple de C qui demande des morceaux de mémoire de plus en plus grands pour déterminer quand une demande de plus échouera:
Si vous ne connaissez pas C, vous pouvez le compiler
gcc virtlimitcheck.c -o virtlimitcheck
, puis l'exécuter./virtlimitcheck
. Il est complètement inoffensif, car le processus n'utilise aucun de l'espace qu'il demande - c'est-à-dire qu'il n'utilise jamais vraiment de RAM.Sur un système 3.11 x86_64 avec un système de 4 Go et 6 Go de swap, j'ai échoué à ~ 7400000 ko; le nombre fluctue, donc peut-être que l'état est un facteur. C'est par hasard proche de l'
CommitLimit
entrée/proc/meminfo
, mais la modification de ce viavm.overcommit_ratio
ne fait aucune différence. Sur un système 3.6.11 32 bits ARM 448 Mo avec 64 Mo de swap, cependant, j'échoue à ~ 230 Mo. Ceci est intéressant car dans le premier cas, la quantité est presque le double de la quantité de RAM, alors que dans le second, elle est d'environ 1/4 - ce qui implique fortement la quantité de swap est un facteur. Cela a été confirmé en désactivant le swap sur le premier système, lorsque le seuil de défaillance est tombé à environ 1,95 Go, un rapport très similaire à la petite boîte ARM.Mais est-ce vraiment par processus? Il semble être. Le programme court ci-dessous demande un morceau de mémoire défini par l'utilisateur, et s'il réussit, attend que vous frappiez retour - de cette façon, vous pouvez essayer plusieurs instances simultanées:
Attention, cependant, il ne s'agit pas strictement de la quantité de RAM et de swap quelle que soit l'utilisation - voir la note de bas de page 5 pour les observations sur les effets de l'état du système.
3
CommitLimit
fait référence à la quantité d'espace d'adressage autorisée pour le système lorsque vm.overcommit_memory = 2. On peut supposer que le montant que vous pouvez allouer devrait être ce moins ce qui est déjà engagé, ce qui est apparemment leCommitted_AS
champ.Une expérience potentiellement intéressante démontrant cela consiste à ajouter
#include <unistd.h>
en haut de virtlimitcheck.c (voir référence 2), etfork()
juste avant lawhile()
boucle. Cela n'est pas garanti de fonctionner comme décrit ici sans une synchronisation fastidieuse, mais il y a de fortes chances que cela fonctionne, YMMV:Cela a du sens - en regardant tmp.txt en détail, vous pouvez voir que les processus alternent leurs allocations de plus en plus grandes (c'est plus facile si vous jetez le pid dans la sortie) jusqu'à ce que l'un, évidemment, ait suffisamment revendiqué que l'autre échoue. Le gagnant est alors libre de tout récupérer jusqu'à
CommitLimit
moinsCommitted_AS
.4 Il convient de mentionner, à ce stade, si vous ne comprenez pas déjà l'adressage virtuel et la pagination de la demande, que ce qui rend possible un engagement excessif, c'est que ce que le noyau alloue aux processus de l'espace utilisateur n'est pas du tout de la mémoire physique - c'est espace d'adressage virtuel . Par exemple, si un processus réserve 10 Mo pour quelque chose, il est présenté comme une séquence d'adresses (virtuelles), mais ces adresses ne correspondent pas encore à la mémoire physique. Lorsqu'une telle adresse est accessible, cela entraîne un défaut de pagepuis le noyau tente de le mapper sur la mémoire réelle afin qu'il puisse stocker une valeur réelle. Les processus réservent généralement beaucoup plus d’espace virtuel qu’ils n’y ont réellement accès, ce qui permet au noyau d’utiliser la RAM le plus efficacement possible. Cependant, la mémoire physique est toujours une ressource limitée et lorsque tout cela a été mappé à l'espace d'adressage virtuel, un certain espace d'adressage virtuel doit être éliminé pour libérer de la RAM.
5 D'abord un avertissement : si vous essayez ceci avec
vm.overcommit_memory=0
, assurez-vous d'enregistrer votre travail d'abord et fermez toutes les applications critiques, car le système sera gelé pendant ~ 90 secondes et certains processus vont mourir!L'idée est d'exécuter une bombe fork qui expire après 90 secondes, les fourches allouant de l'espace et certaines écrivant de grandes quantités de données dans la RAM, tout en se rapportant à stderr.
Compilez ceci
gcc forkbomb.c -o forkbomb
. Tout d'abord, essayez avecsysctl vm.overcommit_memory=2
- vous obtiendrez probablement quelque chose comme:Dans cet environnement, ce type de bombe à fourche ne va pas très loin. Notez que le nombre dans "dit N fourches" n'est pas le nombre total de processus, c'est le nombre de processus dans la chaîne / branche menant à celui-ci.
Maintenant, essayez avec
vm.overcommit_memory=0
. Si vous redirigez stderr vers un fichier, vous pouvez effectuer une analyse grossière par la suite, par exemple:Seuls 15 processus n'ont pas pu allouer 1 Go, ce qui montre que l'heuristique pour overcommit_memory = 0 est affectée par l'état. Combien de processus y avait-il? En regardant la fin de tmp.txt, probablement> 100 000. Maintenant, comment peut-on réellement utiliser le 1 Go?
Huit - ce qui a encore un sens, car à l'époque j'avais environ 3 Go de RAM libre et 6 Go de swap.
Jetez un oeil à vos journaux système après avoir fait cela. Vous devriez voir les scores des rapports du tueur OOM (entre autres); on peut supposer que cela concerne
oom_badness
.la source
Cela ne vous arrivera pas si vous ne chargez que 1 Go de données en mémoire. Et si vous chargez beaucoup plus? Par exemple, je travaille souvent avec des fichiers énormes contenant des millions de probabilités qui doivent être chargés dans R. Cela prend environ 16 Go de RAM.
L'exécution du processus ci-dessus sur mon ordinateur portable entraînera un échange comme un fou dès que mes 8 Go de RAM auront été remplis. Cela, à son tour, ralentira tout car la lecture à partir du disque est beaucoup plus lente que la lecture à partir de la RAM. Et si j'ai un ordinateur portable avec 2 Go de RAM et seulement 10 Go d'espace libre? Une fois que le processus a pris toute la RAM, il remplira également le disque car il écrit pour permuter et il ne me reste plus de RAM et plus d'espace pour swap (les gens ont tendance à limiter le swap à une partition dédiée plutôt qu'à un swapfile pour exactement cette raison). C'est là que le tueur OOM entre en jeu et commence à tuer les processus.
Ainsi, le système peut en effet manquer de mémoire. De plus, les systèmes à très forte permutation peuvent devenir inutilisables bien avant que cela ne se produise simplement en raison des opérations d'E / S lentes dues à la permutation. On veut généralement éviter autant que possible l'échange. Même sur les serveurs haut de gamme dotés de SSD rapides, les performances diminuent clairement. Sur mon ordinateur portable, qui a un lecteur classique de 7200 tr / min, tout échange important rend essentiellement le système inutilisable. Plus il échange, plus il ralentit. Si je ne tue pas le processus offensant rapidement, tout se bloque jusqu'à ce que le tueur OOM intervienne.
la source
Les processus ne sont pas tués quand il n'y a plus de RAM, ils sont tués quand ils ont été trichés de cette façon:
Cela peut se produire même lorsque le système n'échange pas activement, par exemple si la zone d'échange est remplie de pages de mémoire de démons endormis.
Cela ne se produit jamais sur les systèmes d'exploitation qui ne surchargent pas la mémoire. Avec eux, aucun processus aléatoire n'est tué, mais le premier processus demandant de la mémoire virtuelle alors qu'il est épuisé renvoie malloc (ou similaire) par erreur. On lui donne ainsi une chance de bien gérer la situation. Cependant, sur ces systèmes d'exploitation, il peut également arriver que le système manque de mémoire virtuelle alors qu'il reste de la RAM libre, ce qui est assez déroutant et généralement mal compris.
la source
Lorsque la RAM disponible est épuisée, le noyau commence à échanger des bits de traitement sur le disque. En fait, le noyau commence à échanger un lorsque la RAM est presque épuisée: il commence à échanger de manière proactive lorsqu'il a un moment d'inactivité, afin d'être plus réactif si une application nécessite soudainement plus de mémoire.
Notez que la RAM n'est pas utilisée uniquement pour stocker la mémoire des processus. Sur un système sain typique, seulement environ la moitié de la RAM est utilisée par les processus, et l'autre moitié est utilisée pour le cache disque et les tampons. Cela fournit un bon équilibre entre les processus en cours d'exécution et les entrées / sorties de fichiers.
L'espace de swap n'est pas infini. À un certain point, si les processus continuent d'allouer de plus en plus de mémoire, les données de débordement de la RAM rempliront le swap. Lorsque cela se produit, les processus qui tentent de demander plus de mémoire voient leur demande refusée.
Par défaut, Linux surcharge la mémoire. Cela signifie qu'il permet parfois à un processus de s'exécuter avec la mémoire qu'il a réservée, mais non utilisée. La raison principale du sur-engagement est la façon dont fonctionne la fourche . Lorsqu'un processus lance un sous-processus, le processus enfant fonctionne conceptuellement dans une réplique de la mémoire du parent - les deux processus ont initialement de la mémoire avec le même contenu, mais ce contenu divergera à mesure que les processus effectueront des modifications chacun dans leur propre espace. Pour l'implémenter complètement, le noyau devrait copier toute la mémoire du parent. Cela rendrait le forking lent, donc le noyau pratique la copie sur écriture: au départ, l'enfant partage toute sa mémoire avec le parent; chaque fois que l'un des processus écrit sur une page partagée, le noyau crée une copie de cette page pour rompre le partage.
Souvent, un enfant laisse de nombreuses pages intactes. Si le noyau allouait suffisamment de mémoire pour répliquer l'espace mémoire du parent sur chaque fork, beaucoup de mémoire serait gaspillée dans les réservations que les processus enfants n'allaient jamais utiliser. D'où un engagement excessif: le noyau ne réserve qu'une partie de cette mémoire, sur la base d'une estimation du nombre de pages dont l'enfant aura besoin.
Si un processus essaie d'allouer de la mémoire et qu'il n'y a pas assez de mémoire, le processus reçoit une réponse d'erreur et la traite comme bon lui semble. Si un processus demande indirectement de la mémoire en écrivant sur une page partagée qui doit être non partagée, c'est une autre histoire. Il n'y a aucun moyen de signaler cette situation à l'application: elle pense y avoir des données inscriptibles et pourrait même les lire - c'est juste que l'écriture implique une opération légèrement plus élaborée sous le capot. Si le noyau n'est pas en mesure de fournir une nouvelle page de mémoire, tout ce qu'il peut faire est de tuer le processus demandeur ou de tuer un autre processus pour remplir la mémoire.
Vous pourriez penser à ce stade que la suppression du processus de demande est la solution évidente. Mais en pratique, ce n'est pas si bon. Le processus peut être un processus important qui n'a besoin que d'accéder à l'une de ses pages maintenant, alors qu'il peut y avoir d'autres processus moins importants en cours d'exécution. Ainsi, le noyau comprend des heuristiques complexes pour choisir les processus à tuer - le (in) célèbre tueur OOM .
la source
Juste pour ajouter un autre point de vue à partir des autres réponses, de nombreux VPS hébergent plusieurs machines virtuelles sur un serveur donné. Toute machine virtuelle unique aura une quantité spécifiée de RAM pour son propre usage. De nombreux fournisseurs proposent une "RAM éclatée", dans laquelle ils peuvent utiliser la RAM au-delà de la quantité qu'ils ont désignée. Ceci est destiné à être utilisé uniquement à court terme, et ceux qui vont au-delà de cette quantité de temps prolongée peuvent être pénalisés par les processus de destruction de l'hôte pour réduire la quantité de RAM utilisée afin que les autres ne souffrent pas de la machine hôte étant surchargée.
la source
Quelque temps Linux prend de l'espace virtuel externe. C'est la partition de swap. Lorsque Ram est rempli, Linux prend cette zone de swap pour exécuter un processus de faible priorité.
la source