J'exécutais un script Shell avec des commandes pour exécuter plusieurs programmes gourmands en mémoire (2 à 5 Go) dos à dos. Quand je suis retourné pour vérifier la progression de mon script, j'ai été surpris de découvrir que certains de mes processus étaient Killed
, comme me l'a signalé mon terminal. Plusieurs programmes s'étaient déjà achevés successivement avant que les programmes qui étaient plus tard Killed
ne démarrent, mais tous les programmes ont ensuite échoué dans une erreur de segmentation (qui peut ou non être due à un bogue dans mon code, continuez à lire).
J'ai regardé l'historique d'utilisation du cluster particulier que j'utilisais et j'ai vu que quelqu'un a commencé à exécuter plusieurs processus gourmands en mémoire en même temps et, ce faisant, a épuisé la mémoire réelle (et peut-être même l'espace d'échange) disponible pour le cluster. Du mieux que je peux comprendre, ces processus gourmands en mémoire ont commencé à fonctionner à peu près au même moment où j'ai commencé à avoir des problèmes avec mes programmes.
Est-il possible que Linux ait tué mes programmes une fois qu'il a commencé à manquer de mémoire? Et est-il possible que les erreurs de segmentation que j'ai eues plus tard soient dues au manque de mémoire disponible pour exécuter mes programmes (au lieu d'un bug dans mon code)?
Ça peut.
Il existe deux conditions différentes de mémoire insuffisante que vous pouvez rencontrer sous Linux. Ce que vous rencontrez dépend de la valeur de sysctl vm.overcommit_memory
(/proc/sys/vm/overcommit_memory
)
Introduction:
Le noyau peut effectuer ce qu'on appelle une "surcharge de mémoire". C'est alors que le noyau alloue aux programmes plus de mémoire que ce qui est réellement présent dans le système. Cela se fait dans l'espoir que les programmes n'utilisent pas réellement toute la mémoire qu'ils ont allouée, car c'est un phénomène assez courant.
Quand overcommit_memory
est réglé sur 2
, le noyau n'effectue aucune surcharge. Au lieu de cela, lorsqu'un programme se voit allouer de la mémoire, l'accès à cette mémoire est garanti. Si le système n'a pas assez de mémoire libre pour satisfaire une demande d'allocation, le noyau renverra simplement un échec pour la demande. C'est au programme de gérer la situation avec grâce. S'il ne vérifie pas que l'allocation a réussi alors qu'elle a vraiment échoué, l'application rencontrera souvent une erreur de segmentation.
Dans le cas du segfault, vous devriez trouver une ligne comme celle-ci dans la sortie de dmesg
:
[1962.987529] myapp[3303]: segfault at 0 ip 00400559 sp 5bc7b1b0 error 6 in myapp[400000+1000]
Le at 0
signifie que l'application a tenté d'accéder à un pointeur non initialisé, ce qui peut être le résultat d'un appel d'allocation de mémoire échoué (mais ce n'est pas le seul moyen).
Quand overcommit_memory
est réglé sur 0
ou 1
, la surcharge est activée et les programmes sont autorisés à allouer plus de mémoire que ce qui est réellement disponible.
Cependant, lorsqu'un programme souhaite utiliser la mémoire qui lui a été allouée, mais que le noyau constate qu'il n'a pas suffisamment de mémoire pour le satisfaire, il doit récupérer de la mémoire. Il essaie d'abord d'effectuer diverses tâches de nettoyage de la mémoire, telles que le vidage des caches, mais si cela ne suffit pas, il mettra fin à un processus. Cette résiliation est effectuée par l'OOM-Killer. L'OOM-Killer examine le système pour voir quels programmes utilisent quelle mémoire, depuis combien de temps ils s'exécutent, qui les exécute et un certain nombre d'autres facteurs pour déterminer lequel est tué.
Une fois le processus interrompu, la mémoire qu'il utilisait est libérée et le programme qui vient de provoquer l'état de mémoire insuffisante dispose désormais de la mémoire dont il a besoin.
Cependant, même dans ce mode, les programmes peuvent toujours se voir refuser des demandes d'allocation. Quand overcommit_memory
est 0
, le noyau essaie de deviner quand il doit commencer à refuser les demandes d'allocation. Lorsqu'il est défini sur 1
, Je ne sais pas quelle détermination il utilise pour déterminer quand il doit refuser une demande, mais il peut refuser des demandes très volumineuses.
Vous pouvez voir si l'OOM-Killer est impliqué en regardant la sortie de dmesg
et en trouvant des messages tels que:
[11686.043641] Out of memory: Kill process 2603 (flasherav) score 761 or sacrifice child
[11686.043647] Killed process 2603 (flasherav) total-vm:1498536kB, anon-rss:721784kB, file-rss:4228kB
La vérité est que quelle que soit la façon dont vous le regardez - que votre processus soit étouffé à cause du gestionnaire de mémoire du système ou à cause d'autre chose - c'est toujours un bug. Qu'est-il arrivé à toutes ces données que vous veniez de traiter en mémoire? Il aurait dû être enregistré.
Tandis que overcommit_memory=
est la façon la plus générale de configurer la gestion des MOO Linux, elle est également réglable par processus comme:
echo [-+][n] >/proc/$pid/oom_adj
En utilisant -17
dans ce qui précède exclura un processus de la gestion de la mémoire insuffisante. Ce n'est probablement pas une bonne idée en général, mais si vous êtes à la recherche de bogues, cela pourrait valoir la peine - surtout si vous souhaitez savoir s'il s'agissait de MOO o votre code. L'incrémentation positive du nombre rendra le processus plus susceptible d'être tué dans un événement MOO, ce qui pourrait vous permettre de mieux renforcer la résilience de votre code dans des situations de faible mémoire et de vous assurer de sortir correctement si nécessaire.
Vous pouvez vérifier les paramètres actuels du gestionnaire OOM par processus comme:
cat /proc/$pid/oom_score
Sinon, vous pourriez devenir suicidaire:
sysctl vm.panic_on_oom=1
sysctl kernel.panic=X
Cela permettra à l'ordinateur de redémarrer en cas de problème de mémoire insuffisante. Vous définissez X
ci-dessus sur le nombre de secondes pendant lesquelles vous souhaitez que l'ordinateur s'arrête après une panique du noyau avant de redémarrer. Devenir fou.
Et si, pour une raison quelconque, vous décidez que vous l'aimez, rendez-le persistant:
echo "vm.panic_on_oom=1" >> /etc/sysctl.conf
echo "kernel.panic=X" >> /etc/sysctl.conf