web-dev-qa-db-fra.com

Que se passe-t-il lorsqu'un fichier contenant 100% de pages dans le cache de pages est modifié par un autre processus

Je sais que lorsqu'une page de cache de page est modifiée, elle est marquée comme sale et nécessite une réécriture, mais que se passe-t-il lorsque:

Scénario: Le fichier/apps/EXE, qui est un fichier exécutable, est paginé complètement dans le cache de pages (toutes ses pages sont dans le cache/mémoire) et exécuté par le processus P

La version continue remplace ensuite/apps/EXE par un tout nouvel exécutable.

Hypothèse 1: Je suppose que le processus P (et toute autre personne ayant un descripteur de fichier référençant l'ancien exécutable) continuera à utiliser l'ancien, dans la mémoire/apps/EXE sans problème, et tout nouveau processus qui tente d'exécuter ce chemin obtiendra le nouvel exécutable.

Hypothèse 2: Je suppose que si toutes les pages du fichier ne sont pas mappées en mémoire, les choses iront bien jusqu'à ce qu'il y ait une erreur de page nécessitant des pages du fichier qui ont été remplacées, et probablement une erreur de segmentation arrivera?

Question 1: Si vous bloquez toutes les pages du fichier avec quelque chose comme vmtouch, cela change-t-il le scénario?

Question 2: Si/apps/EXE est sur un NFS distant, cela ferait-il une différence? (Je suppose que non)

Veuillez corriger ou valider mes 2 hypothèses et répondre à mes 2 questions.

Supposons qu'il s'agit d'une boîte CentOS 7.6 avec une sorte de noyau 3.10.0-957.el7

Mise à jour: En y réfléchissant davantage, je me demande si ce scénario n'est pas différent de tout autre scénario de page sale.

Je suppose que le processus qui écrit le nouveau binaire fera une lecture et récupérera toutes les pages de cache car il est entièrement paginé, puis toutes ces pages seront marquées comme sales. S'ils sont verrouillés, ce ne seront que des pages inutiles occupant la mémoire centrale après que le nombre de références soit passé à zéro.

Je soupçonne qu'à la fin des programmes en cours d'exécution, tout le reste utilisera le nouveau binaire. En supposant que tout est correct, je suppose que ce n'est intéressant que lorsque seule une partie du fichier est paginée.

14
Gregg Leventhal

La version continue remplace ensuite/apps/EXE par un tout nouvel exécutable.

C'est la partie importante.

La façon dont un nouveau fichier est publié est de créer un nouveau fichier (par exemple /apps/EXE.tmp.20190907080000), D'écrire le contenu, de définir les autorisations et la propriété et enfin le renommer (2) en lui donnant le nom final /apps/EXE, en remplaçant l'ancien fichier.

Le résultat est que le nouveau fichier a un nouveau numéro d'inode (ce qui signifie, en fait, c'est un fichier différent.)

Et l'ancien fichier avait son propre numéro d'inode, qui est en fait toujours autour même si le nom du fichier ne le pointe plus (ou il n'y a pas de fichier les noms pointant vers cet inode.)

Donc, la clé ici est que lorsque nous parlons de "fichiers" sous Linux, nous parlons le plus souvent vraiment d '"inodes" car une fois qu'un fichier a été ouvert, l'inode est la référence que nous gardons pour le fichier.

Hypothèse 1: Je suppose que le processus P (et toute autre personne avec un descripteur de fichier référençant l'ancien exécutable) continuera à utiliser l'ancien, dans la mémoire/apps/EXE sans problème, et tout nouveau processus qui tente d'exécuter ce chemin obtiendra le nouvel exécutable.

Correct.

Hypothèse 2: Je suppose que si toutes les pages du fichier ne sont pas mappées en mémoire, les choses iront bien jusqu'à ce qu'il y ait une erreur de page nécessitant des pages du fichier qui ont été remplacées, et probablement une erreur de segmentation arrivera?

Incorrect. L'ancien inode est toujours là, donc les erreurs de page du processus utilisant l'ancien binaire seront toujours en mesure de trouver ces pages sur le disque.

Vous pouvez voir certains effets de cela en regardant le lien symbolique /proc/${pid}/exe (Ou, de manière équivalente, la sortie lsof) pour le processus exécutant l'ancien binaire, qui montrera /app/EXE (deleted) à indiquer que le nom n'est plus là mais que l'inode est toujours là.

Vous pouvez également voir que l'espace disque utilisé par le binaire ne sera libéré qu'après la fin du processus (en supposant que c'est le seul processus avec cet inode ouvert.) Vérifiez la sortie de df avant et après avoir tué le processus, vous aurez le voir baisser de la taille de cet ancien binaire que vous pensiez ne plus être là.

BTW, ce n'est pas seulement avec les binaires, mais avec tous les fichiers ouverts. Si vous ouvrez un fichier dans un processus et supprimez le fichier, le fichier sera conservé sur le disque jusqu'à ce que ce processus ferme le fichier (ou meurt.) De la même manière que les liens physiques gardent un compteur du nombre de noms pointant vers un inode sur le disque, le Le pilote du système de fichiers (dans le noyau Linux) garde un compteur du nombre de références à cet inode en mémoire , et ne libérera l'inode du disque qu'une fois des références du système en cours d'exécution ont également été publiées.

Question 1: Si vous bloquez toutes les pages du fichier avec quelque chose comme vmtouch, cela change-t-il le scénario

Cette question est basée sur l'hypothèse incorrecte 2 selon laquelle le non-verrouillage des pages entraînera des erreurs de segmentation. Ce ne sera pas le cas.

Question 2: Si/apps/EXE est sur un NFS distant, cela ferait-il une différence? (Je suppose que non)

C'est destiné à fonctionner de la même manière et la plupart du temps, mais il y a des "accrochages" avec NFS.

Parfois, vous pouvez voir les artefacts de la suppression d'un fichier qui est toujours ouvert dans NFS (apparaît comme un fichier caché dans ce répertoire.)

Vous avez également un moyen d'attribuer des numéros de périphérique aux exportations NFS, pour vous assurer que ceux-ci ne seront pas "réorganisés" lors du redémarrage du serveur NFS.

Mais l'idée principale est la même. Le pilote client NFS utilise toujours des inodes et essaiera de conserver les fichiers (sur le serveur) pendant que l'inode est toujours référencé.

13
filbranden

Hypothèse 2: je suppose que si toutes les pages du fichier ne sont pas mappées en mémoire, les choses iront bien jusqu'à ce qu'il y ait une erreur de page nécessitant des pages du fichier qui ont été remplacées, et probablement une erreur de segmentation se produira?

Non, cela ne se produira pas, car le noyau ne vous laissera pas ouvrir pour écrire et remplacer quoi que ce soit dans un fichier en cours d'exécution. Une telle action échouera avec ETXTBSY [1]:

cp /bin/sleep sleep; ./sleep 3600 & echo none > ./sleep
[9] 5332
bash: ./sleep: Text file busy

Lorsque dpkg, etc. met à jour un binaire, il ne l'écrase pas, mais utilise rename(2) qui pointe simplement l'entrée du répertoire vers un fichier complètement différent, et tous les processus qui ont encore des mappages ou des poignées ouvertes vers l'ancien fichier continuera à l'utiliser sans problème.

[1] la protection ETXBUSY n'est pas étendue à d'autres fichiers qui peuvent également être considérés comme du "texte" (= live code/exécutable): bibliothèques partagées, Java classes, etc; la modification d'un tel fichier tout en étant mappé par un autre processus entraînera le processus de se bloquer. Sous linux, l'éditeur de liens dynamique passe consciencieusement le Indicateur MAP_DENYWRITE Sur mmap(2), mais ne vous y trompez pas - cela n'a aucun effet. Exemple:

$ cc -xc - <<<'void lib(){}' -shared -o lib.so
$ cc -Wl,-rpath=. lib.so -include unistd.h -xc - <<<'
   extern void lib();
   int main(){ truncate("lib.so", 0); lib(); }
'
./a.out
Bus error
8
mosvy

la réponse de filbranden est correcte en supposant que le processus de libération continue effectue un remplacement atomique approprié des fichiers via rename. Si ce n'est pas le cas, mais modifie le fichier sur place, les choses sont différentes. Cependant, votre modèle mental se trompe toujours.

Il n'y a aucune possibilité que les choses soient modifiées sur le disque et incompatibles avec le cache de page, car le cache de page est la version canonique et celui qui est modifié. Toute écriture dans un fichier s'effectue via le cache de pages. S'il y est déjà présent, les pages existantes sont modifiées. Si elle n'est pas encore présente, les tentatives de modification d'une page partielle entraîneront la mise en cache de la page entière, suivie d'une modification comme si elle était déjà mise en cache. Les écritures qui s'étendent sur une page entière ou plus peuvent (et presque sûrement) optimiser l'étape de lecture en les paginant. Dans tous les cas, il n'existe qu'une seule version canonique modifiable d'un fichier (*), celle du cache de page .

(*) J'ai légèrement menti. Pour NFS et d'autres systèmes de fichiers distants, il peut y en avoir plus d'un et ils (généralement en fonction de celui et des options de montage et côté serveur utilisés) n'implémentent pas correctement l'atomicité et la sémantique de commande pour les écritures. C'est pourquoi beaucoup d'entre nous les considèrent comme fondamentalement cassés et refusent de les utiliser dans des situations où il y aura des écritures en même temps que leur utilisation.