tuto3: Rewrite OOM part
This commit is contained in:
parent
412d69a649
commit
6665cfbace
@ -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.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user