tuto3: Rewrite OOM part

This commit is contained in:
nemunaire 2022-10-18 06:15:03 +02:00
parent 412d69a649
commit 6665cfbace

View File

@ -3,55 +3,87 @@
Gestion de la mémoire Gestion de la mémoire
===================== =====================
Linux a une gestion de la mémoire bien particulière[^vm-overcommit] : par Linux a une gestion de la mémoire bien particulière[^vm-overcommit] : en effet,
défaut, `malloc(3)` ne retournera jamais `NULL`. En se basant sur l'euristique par défaut, `malloc(3)` ne retournera jamais `NULL`. En se basant sur
qu'un bloc mémoire demandé ne sera pas utilisé directement et que de nombreux l'euristique qu'un bloc mémoire demandé ne sera pas utilisé directement et que
process ne feront pas un usage total des blocks qu'ils ont alloués, le noyau de nombreux process ne feront pas un usage total des blocs qu'ils ont alloués,
permet d'allouer plus de mémoire qu'il n'y en a réellement disponible. La le noyau permet d'allouer plus de mémoire qu'il n'y en a réellement
mémoire est donc utilisée de manière plus efficace. disponible. La mémoire est ainsi utilisée de manière plus efficace.
[^vm-overcommit]: Dépendant de la valeur de `/proc/sys/vm/overcommit_memory`, [^vm-overcommit]: Cela dépend de la valeur de `/proc/sys/vm/overcommit_memory`,
généralement 0. Voir généralement 0. Voir
<https://www.kernel.org/doc/html/latest/vm/overcommit-accounting.html>. <https://www.kernel.org/doc/html/latest/vm/overcommit-accounting.html>.
Mais évidemment, cela peut donner lieu à des situations où le noyau n'est plus Mais évidemment, cela peut donner lieu à des situations où, au moment où un
en mesure de trouver de blocs physiquement disponibles, alors qu'ils avaient processus se met à utiliser un nouveau bloc de mémoire (reçu du noyau d'un
effectivement été alloués au processus. Pour autant, ce n'est pas une raison appel précédent à `malloc(3)`) qu'il n'utilisait pas jusque là, le noyau se
pour tuer ce processus, car il est peut-être vital pour le système (peut-être trouve dans l'impossibilité d'attribuer un bloc physiquement disponible, car il
est-ce `init` qui est en train de gérer le lancement d'un nouveau daemon). On n'y en a tout simplement plus (y compris via le swap).
dit alors que le noyau est *Out-Of-Memory*.
Puisque le noyau ne peut pas honorer sa promesse et qu'il n'a plus la
possibilité de retourner `NULL` au programme qui réclamme sa mémoire (il s'agit
sans doute d'une simple assignation de variable à ce stade), il faut trouver
une solution si l'on veut pouvoir continuer l'exécution du programme.
Le noyau pourrait choisir de suspendre l'exécution du processus tant qu'il n'y
a pas de nouveau bloc mémoire disponible... mais, à moins qu'un processus se
termine ou libère de la mémoire, on risque de se retrouver face à une situation
de faillite où tous les processus seraient suspendus, sans garantie qu'une
solution se produise à un moment donné.
Pour être certain de récupérer de la mémoire, le noyau n'a pas d'autre solution
que de tuer un processus. L'issue la plus simple est de tuer le processus qui
est en train d'accéder à la plage de mémoire que le noyau ne peut pas
honorer. Pour autant, ce n'est pas une raison pour tuer ce processus, car il
est peut-être vital pour le système (peut-être est-ce `init` qui est en train
de gérer le lancement d'un nouveau daemon). On dit alors que le noyau est
*Out-Of-Memory*.
Pour se sortir d'une telle situation, et après avoir tenté de vider les caches, Pour se sortir d'une telle situation, et après avoir tenté de vider les caches,
il lance son arme ultime pour retrouver au plus vite de la mémoire : il lance son arme ultime pour retrouver au plus vite de la mémoire :
l'*Out-Of-Memory killer*. l'*Out-Of-Memory killer*.
## OOM killer ## Trouver un coupable
Selon un algorithme dont on raconte qu'il ne serait pas basé entièrement sur Tous les processus, au cours de leur exécution, disposent d'un score permettant
l'aléatoire[^oom-algo], un processus est tiré au sort (plus un processus occupe au système de savoir à tout moment quel processus apporterait le plus de gain à
de mémoire et plus il a de chance d'être tiré au sort) par l'OOM killer. Le être éliminé, si la mémoire venait à manquer.
sort qui lui est réservé est tout simplement une mort brutale, pour permettre
au système de disposer à nouveau de mémoire disponible. Si cela n'est pas
suffisant, un ou plusieurs autres processus peuvent être tués à tour de rôle,
jusqu'à ce que le système retrouve sa sérénité.
[^oom-algo]: <https://linux-mm.org/OOM_Killer> Lorsqu'une situation d'OOM est déclarée, le noyau tue le processus avec le
score le plus élevé à ce moment là.
Pour connaître le score actuel d'un processus, on affiche le contenu du fichier
`oom_score` dans son dossier de `/proc` :
## Esquiver l'OOM killer ? <div lang="en-US">
```bash
42sh$ cat /proc/self/oom_score
666
42sh$ cat /proc/1/oom_score
0
```
</div>
Au sein d'un *cgroup* *memory*, les fichiers `memory.oom_control` (v1) ou Le score est établi entre 0 et 1000.
`memory.events` (v2) peuvent être utilisés afin de recevoir une notification du
noyau avant que l'OOM-killer ne s'attaque à un processus de ce groupe.
Grâce à cette notification, il est possible de figer le processus pour Le but étant de récupérer le plus vite possible, le plus de mémoire possible,
l'envoyer sur une autre machine. Et ainsi libérer la mémoire avant que l'OOM le score est établi selon la quantité de mémoire que le processus occupe.
killer ne passe.
Voici un script pour visualiser les programmes ayant les plus gros scores sur
votre système :
<div lang="en-US">
```bash
ps -A | while read pid _ _ comm; do
echo $(cat /proc/$pid/oom_score) $comm;
done | sort -h
```
</div>
A priori, la dernière ligne montre le processus ayant le plus de chance d'être
tué en cas de passage proche de l'OOM-killer.
Jetez un œil à [cet article paru sur LWN](https://lwn.net/Articles/590960/) à
ce sujet :\
<https://lwn.net/Articles/590960/>
::::: {.exercice} ::::: {.exercice}
@ -60,6 +92,75 @@ ce sujet :\
Continuons l'exercice précédent où nous avions [fixé les Continuons l'exercice précédent où nous avions [fixé les
limites](#Fixer-des-limites) de mémoire que pouvaient réserver les processus de limites](#Fixer-des-limites) de mémoire que pouvaient réserver les processus de
notre groupe. Que se passe-t-il alors si `memhog` dépasse la quantité de notre groupe. Que se passe-t-il alors si `memhog` dépasse la quantité de
mémoire autorisée dans le `cgroup` ? mémoire autorisée au sein du `cgroup` ?
<div lang="en-US">
```bash
42sh$ echo 512M > /sys/fs/cgroup/memory/MYNAME/memory.limit_in_bytes
42sh$ ./monitor MYNAME memhog 500
................ # OK
42sh$ ./monitor MYNAME memhog 700
..............Killed
```
</div>
::::: :::::
Eh oui, l'OOM-killer passe également lorsqu'un `cgroup` atteint la limite de
mémoire qui lui est réservé. Dans ce cas évidemment, les processus pris en
compte sont ceux contenus dans le `cgroup`.
## Esquiver l'OOM killer ?
Le passage de l'OOM killer relevant parfois de la roulette russe, il peut être
intéressant, pour certain processus, de vouloir faire en sorte qu'ils aient
moins de chance d'arriver en tête du classement, même s'ils occupent beaucoup
de mémoire. On pourrait par exemple vouloir que des services importants ou des
programmes contenant notre travail en cours (potentiellement non-enregistré !)
ne soient pas ciblés à moins de ne plus avoir d'autre choix.
Le sujet étant très épineux, et aucune solution ou algorithme n'ayant
réellement démontré sa supériorité pour évincer la tâche idéale, nous avons la
possibilité de modifier le score à la hausse ou à la baisse.
Le fichier `oom_score_adj` peut contenir un valeur entre -1000 et +1000, cette
valeur vient s'ajouter au score du processus à chaque prochain calcul. Une
valeur négative va faire réduire le score et réduire d'autant les chances que
le processus soit ciblé, tandis qu'une valeur positive va augmenter le score et
donc accroître les chances que le processus soit ciblé.
La valeur spéciale de `-1000` fait que le processus ne sera pas considéré comme
une cible potentielle. Au même titre que les *thread*s du noyau ou le processus
`init` du système.
## Être notifié sur l'état de la mémoire
Au sein d'un *cgroup* *memory*, les fichiers `memory.oom_control` (v1) ou
`memory.events` (v2) peuvent être utilisés afin de recevoir une notification du
noyau avant que l'OOM-killer ne s'attaque à un processus de ce groupe.
L'idée est de créer un *eventfd* avec `eventfd(2)`, d'ouvrir le fichier
`memory.oom_control` ou `memory.events` du groupe pour lequel on veut être
notifié, pour obtenir un *file descriptor* ; enfin écrire dans le fichier
`cgroup.event_control` le numéro de l'*eventfd* puis du *file descriptor* sur
`memory.oom_control`, séparé par une espace.
On attend ensuite la notification avec :
<div lang="en-US">
```c
uint64_t ret;
read(<fd of eventfd()>, &ret, sizeof(ret));
```
</div>
D'autres notifications peuvent être mises en place, selon le même principe sur
d'autres fichiers, notamment `memory.usage_in_bytes`, pour être prévenu dès
lors que l'on franchi dans un sens ou dans l'autre un certain palier. Le palier
qui nous intéresse est à indiquer comme troisième argument à
`cgroup.event_control`.
Dans la version 2 des *cgroups*, il est même possible d'utiliser `inotify` pour
surveiller les changements.