Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Docker et occupation m

19 réponses
Avatar
Yliur
Bonjour

Docker permet de mettre en commun des fichiers entre plusieurs
conteneurs, grâce aux images "ancêtres" des images finales.

Avoir des images ayant une ancêtre commune permet donc de gagner de la
place sur le disque.

Par contre lors de l'exécution, qu'est-ce qui est économisé en mémoire ?
Je suppose que c'est le noyau qui gère le cache système, donc accéder à
un fichier d'une image ancêtre depuis deux conteneurs permet de partager
ce cache ?

D'un point de vue bibliothèques dynamiques, comment ça se passe ? Là en
fait la question dépasse le cadre de docker : comment une bibliothèque
dynamique est-elle "chargée" en mémoire ? Elle apparaît quelque part dans
la mémoire virtuelle d'un processus ? Est-ce que ça implique qu'elle soit
chargée quelque part ou bien c'est une simple correspondance entre ces
adresses virtuelles et le fichier représentant la bibliothèque, en se
contentant d'utiliser le cache système au fur et à mesure que des pages
sont nécessaire ?

Même question pour un processus d'ailleurs : est-ce que le contenu de
l'exécutable est chargé d'une manière spéciale en mémoire, ou bien juste
comme n'importe quel fichier ? En dehors des "méta-données" présentes
dans le fichier exécutable, forcément interprétées par le système au
moment du démarrage de l'exécutable en question.

Et si ce fichier exécutable ou d'une bibliothèque est supprimé lors de
l'exécution, est-ce que ça pose un problème ou bien ce fichier est
considéré comme ouvert et à ce titre son contenu n'est pas supprimé tant
qu'un processus détient une référence à son descripteur ?

Oui, on s'éloigne de docker, mais je me rends compte que ces points sont
assez flous pour moi et que ça m'aurait sans doute permis de répondre à
ma question sur docker...

Merci pour vos lumières :)

Yliur

10 réponses

1 2
Avatar
Nicolas George
Yliur , dans le message <q53jgd$nck$, a écrit :
Docker permet de mettre en commun des fichiers entre plusieurs
conteneurs, grâce aux images "ancêtres" des images finales.

Ah, les micro-optimisations pour rendre les catastrophes présentables.
fait la question dépasse le cadre de docker : comment une bibliothèque
dynamique est-elle "chargée" en mémoire ? Elle apparaît quelque part dans
la mémoire virtuelle d'un processus ? Est-ce que ça implique qu'elle soit
chargée quelque part ou bien c'est une simple correspondance entre ces
adresses virtuelles et le fichier représentant la bibliothèque, en se
contentant d'utiliser le cache système au fur et à mesure que des pages
sont nécessaire ?

Unix possède la notion d'appel système « mmap() », qui permet de faire
apparaître un fichier (ou équivalent) comme une zone dans l'espace
d'adresses virtuelles d'un processus. Si c'est en lecture seule, c'est
réalisé en faisant apparaître directement les pages du cache dans
l'espace d'adressage du processus. Il y a pas mal d'optimisation faite
là-dessus :
- Pour la lecture, les pages sont initialement marquées comme invalides,
ce qui fait que le processus, quand il y accède, déclenche une faute
de page, le truc qui d'habitude se termine par « segmentation fault ».
Mais cette fois-ci, c'est réglé pour que la faute de page conduise au
chargement de la page (et au chargement anticipé des pages suivantes)
avant de laisser le processus reprendre.
- Pour l'écriture, quand elle est autorisée, le mécanisme utilise
également des fautes de page. Il y a alors deux possibilités : si le
fichier est mappé en privé, le noyau alloue une page au processus,
comme n'importe quelle allocation mémoire, et y fait une copie de la
page en question (copy-on-write). S'il est mappé en partagé, la page
du cache est marquée comme modifiée et sera écrite sur disque.
Les exécutables et les bibliothèques partagées sont chargés en utilisant
ce mécanisme. C'est fait par le noyau pour l'exécutable et par le
chargeur dynamique pour les bibliothèques. On peut l'observer dans
/proc/$PID/maps.
Et si ce fichier exécutable ou d'une bibliothèque est supprimé lors de
l'exécution, est-ce que ça pose un problème ou bien ce fichier est
considéré comme ouvert et à ce titre son contenu n'est pas supprimé tant
qu'un processus détient une référence à son descripteur ?

Du point de vue du filesystem, un fichier mmapé est un fichier ouvert,
donc s'il est dé-lié, sa suppression effective attendra qu'il soit
dé-mmappé, souvent à la fin des processus correspondants.
Ce qui est plus inquiétant, c'est ce qui se passe si le fichier est
modifié directement. Pour l'exécutable principal, le noyau l'interdit :
ssecem ~ $ cp =sleep /tmp
ssecem ~ $ /tmp/sleep 60 &
[1] 8696
ssecem ~ $ ls >> /tmp/sleep
zsh: text file busy: /tmp/sleep
zsh: exit 1 ls --color=tty -FT0 >> /tmp/sleep
Pour les bibliothèques, cette protection n'arrive pas, ce qui fait qu'on
peut observer des comportements étranges, par exemple si on a lancé un
daemon directement depuis un arbre de compilation et qu'on recompile.
Avatar
Marc SCHAEFER
Yliur wrote:
Docker permet de mettre en commun des fichiers entre plusieurs
conteneurs, grâce aux images "ancêtres" des images finales.

J'aurais l'impression que les images vraisemblablement en
`copy-on-write' sont montées comme des fs indépendants,
donc il n'y aura pas de partage de zone de code (programme
ou so), car les fichiers sont différents.
En plus, sur les plateformes modernes, souvent, le véritable code
exécuté est en fait une donnée, non partagée sauf fork()
préalable, d'un interprète ou d'un exécuteur de bytecode en JVM.
Pour partager le plus possibles de choses entre VM génériques,
et pas seulement du `text' (code en lecture seulement),
container ou non, je dirais qu'il faut investiguer ça:
https://en.wikipedia.org/wiki/Kernel_same-page_merging
Avatar
Yliur
Le Tue, 26 Feb 2019 15:15:29 +0000, Nicolas George a écrit :
Yliur , dans le message <q53jgd$nck$, a écrit :
Docker permet de mettre en commun des fichiers entre plusieurs
conteneurs, grâce aux images "ancêtres" des images finales.

Ah, les micro-optimisations pour rendre les catastrophes présentables.

?
J'ai des choses à exécuter sur un système d'exploitation donné, je trouve
docker pratique dans ce contexte, plutôt qu'une machine virtuelle
complète par exemple.
Et dans le cadre du déploiement de différentes versions/instances d'une
appli, le cas du partage de fichiers communs (le fonctionnement normal de
docker) est assez courant : les conteneurs déployés ont bien une base
commune.
Je ne comprends pas où tu situes la catastrophe...
fait la question dépasse le cadre de docker : comment une bibliothèque
dynamique est-elle "chargée" en mémoire ? Elle apparaît quelque part
dans la mémoire virtuelle d'un processus ? Est-ce que ça implique
qu'elle soit chargée quelque part ou bien c'est une simple
correspondance entre ces adresses virtuelles et le fichier représentant
la bibliothèque, en se contentant d'utiliser le cache système au fur et
à mesure que des pages sont nécessaire ?

Unix possède la notion d'appel système « mmap() », qui permet de faire
apparaître un fichier (ou équivalent) comme une zone dans l'espace
d'adresses virtuelles d'un processus. Si c'est en lecture seule, c'est
réalisé en faisant apparaître directement les pages du cache dans
l'espace d'adressage du processus. Il y a pas mal d'optimisation faite
là-dessus :
- Pour la lecture, les pages sont initialement marquées comme invalides,
ce qui fait que le processus, quand il y accède, déclenche une faute
de page, le truc qui d'habitude se termine par « segmentation fault ».
Mais cette fois-ci, c'est réglé pour que la faute de page conduise au
chargement de la page (et au chargement anticipé des pages suivantes)
avant de laisser le processus reprendre.
- Pour l'écriture, quand elle est autorisée, le mécanisme utilise
également des fautes de page. Il y a alors deux possibilités : si le
fichier est mappé en privé, le noyau alloue une page au processus,
comme n'importe quelle allocation mémoire, et y fait une copie de la
page en question (copy-on-write). S'il est mappé en partagé, la page
du cache est marquée comme modifiée et sera écrite sur disque.

Ces pages dans la mémoire virtuelle de processus, elles correspondent à
des blocs du disque directement ? Ou bien un mécanisme de correspondance
plus complexe (par exemple les octets n à m d'un fichier, peu importe où
il est stocké) ?
En fait je ne vois pas bien si ce cache se trouve à très bas niveau, au
niveau du stockage ou plutôt au-dessus de la notion de fichier et donc
géré par le système de fichiers.
Au passage, cette histoire de mmap sur des fichiers, il me semble avoir
vu que c'était parfois utilisé comme optimisation de lecture de fichiers
classiques (dont on veut juste lire le contenu) : c'est parce qu'au lieu
d'allouer un tableau d'octets en mémoire et de copier le contenu d'un
fichier dedans on se contente de faire une association entre les adresses
et le contenu du fichier, donc on évite une allocation et la recopie de
tous les octets en mémoire ? Et peut-être des appels systèmes pour les
lectures successives de blocs dans le fichier.
Les exécutables et les bibliothèques partagées sont chargés en utilisant
ce mécanisme. C'est fait par le noyau pour l'exécutable et par le
chargeur dynamique pour les bibliothèques. On peut l'observer dans
/proc/$PID/maps.

Le contenu de /proc/$PID/maps laisse entendre que c'est plutôt géré au
niveau du système de fichiers, non ? Il y a des chemins vers les fichiers
dans ce qui s'affiche. À moins que ce ne soit qu'une indication pour s'y
retrouver ?
Et si ce fichier exécutable ou d'une bibliothèque est supprimé lors de
l'exécution, est-ce que ça pose un problème ou bien ce fichier est
considéré comme ouvert et à ce titre son contenu n'est pas supprimé
tant qu'un processus détient une référence à son descripteur ?

Du point de vue du filesystem, un fichier mmapé est un fichier ouvert,
donc s'il est dé-lié, sa suppression effective attendra qu'il soit
dé-mmappé, souvent à la fin des processus correspondants.
Ce qui est plus inquiétant, c'est ce qui se passe si le fichier est
modifié directement. Pour l'exécutable principal, le noyau l'interdit :
ssecem ~ $ cp =sleep /tmp ssecem ~ $ /tmp/sleep 60 &
[1] 8696 ssecem ~ $ ls >> /tmp/sleep zsh: text file busy: /tmp/sleep
zsh: exit 1 ls --color=tty -FT0 >> /tmp/sleep
Pour les bibliothèques, cette protection n'arrive pas, ce qui fait qu'on
peut observer des comportements étranges, par exemple si on a lancé un
daemon directement depuis un arbre de compilation et qu'on recompile.

D'accord, ouvrir le fichier même pour écrasement/remplacement n'en fait
pas un nouveau fichier de ce point de vue...
Du coup une mise à jour de paquets alors que du code s'exécute est assez
dangereuse.
Avatar
Yliur
Le Tue, 26 Feb 2019 18:02:28 +0100, Marc SCHAEFER a écrit :
Yliur wrote:
Docker permet de mettre en commun des fichiers entre plusieurs
conteneurs, grâce aux images "ancêtres" des images finales.

J'aurais l'impression que les images vraisemblablement en
`copy-on-write' sont montées comme des fs indépendants,
donc il n'y aura pas de partage de zone de code (programme ou so), car
les fichiers sont différents.

Il s'agit sans doute de systèmes de fichiers indépendants au sens où
l'arborescence n'est pas la même, mais le contenu est partagé (l'espace
n'est effectivement pas occupé deux fois sur le disque).
Dans le cas de SF représentés par des arbres inaltérables (btrfs et ses
sous-volumes par exemple), la notion d'indépendance est encore un peu
plus floue.
Et dans un de mes tests, changer le propriétaire d'un fichier a
manifestement mené à recréer le fichier (espace double occupé sur le
disque au final).
Et donc si le contenu du fichier n'est représenté qu'une seule fois, ça
ne repose pas sur du cache de bas niveau (pages du disque lui-même, en
dessous de la notion de fichier) ? Ce cache se trouve au-dessus de la
notion de fichier ?
En plus, sur les plateformes modernes, souvent, le véritable code
exécuté est en fait une donnée, non partagée sauf fork() préalable, d'un
interprète ou d'un exécuteur de bytecode en JVM.
Pour partager le plus possibles de choses entre VM génériques, et pas
seulement du `text' (code en lecture seulement),
container ou non, je dirais qu'il faut investiguer ça:
https://en.wikipedia.org/wiki/Kernel_same-page_merging

D'accord, merci.
Ce n'était pas pour appliquer réellement des optimisations, plutôt par
curiosité et mieux comprendre comment tout ça fonctionne.
Avatar
Marc SCHAEFER
Yliur wrote:
Et donc si le contenu du fichier n'est représenté qu'une seule fois, ça
ne repose pas sur du cache de bas niveau (pages du disque lui-même, en
dessous de la notion de fichier) ? Ce cache se trouve au-dessus de la
notion de fichier ?

Effectivement, j'aurais l'impression que si le numéro d'inode diffère
(a fortiori s'il s'agit de deux points de montage hors cas du mount --bind),
le partage ne peut avoir lieu. De mémoire, le partage de zones de
`text' (code) est lié au VFS (abstraction filesystem: je pense plus des
dentries que des numéros d'inodes, mais il y a une correspondance).
Pour vraiment répondre à cette question il faudrait consulter l'implémentation
du fs cow que vous utilisez. S'il s'agit d'un simple overlay sur un FS
de plus bas niveau (style unionfs ou le plus ancien FiST), ça pourrait
fonctionner. Si c'est autre chose, style au niveau du bloc device, non.
Il pourrait toujours y avoir une optimisation de chargement (finalement
le même bloc du disque sous-jacent déjà dans le buffer cache), mais pas
de partage de bibliothèques ou de segment `text'.
Mais je n'en jurerais pas, mon expérience avec le VFS et le cache de
Linux date du kernel 2.4.
Avatar
Nicolas George
Yliur , dans le message <q55nbe$kf1$, a écrit :
J'ai des choses à exécuter sur un système d'exploitation donné, je trouve
docker pratique dans ce contexte, plutôt qu'une machine virtuelle
complète par exemple.
Et dans le cadre du déploiement de différentes versions/instances d'une
appli, le cas du partage de fichiers communs (le fonctionnement normal de
docker) est assez courant : les conteneurs déployés ont bien une base
commune.
Je ne comprends pas où tu situes la catastrophe...

Tu présentes les choses comme si la problématique que tu cherches à
résoudre était une donnée immuable, mais ce n'est pas le cas. Tu as le
besoin que tu évoques en premier parce que le logiciel que tu cherches à
utiliser est mal packagé, et les logiciels sont de plus en plus souvent
mal packagés parce que des solutions comme docker existent pour aider à
les faire tourner.
Et en attendant, au revoir les mises à jour de sécurité sur les
bibliothèques.
Ces pages dans la mémoire virtuelle de processus, elles correspondent à
des blocs du disque directement ? Ou bien un mécanisme de correspondance
plus complexe (par exemple les octets n à m d'un fichier, peu importe où
il est stocké) ?

Elles correspondent à des pages, de même taille, au niveau d'abstraction
« système de fichiers ». C'est un élément fondamental de la gestion
mémoire de Linux.
Quant au lien avec le disque dur, il dépend du filesystem. Pour
beaucoup, la structure en blocs des fichiers est alignée avec la
structure en blocs du périphérique sous-jacent. Dans ce cas, les buffers
de cache du périphérique servent directement de pages de cache. Mais il
y a certains systèmes de fichiers qui font par exemple des économies sur
les blocs en fin de fichier, et dans ce cas il y a un niveau
d'indirection.
Au passage, cette histoire de mmap sur des fichiers, il me semble avoir
vu que c'était parfois utilisé comme optimisation de lecture de fichiers
classiques (dont on veut juste lire le contenu) : c'est parce qu'au lieu
d'allouer un tableau d'octets en mémoire et de copier le contenu d'un
fichier dedans on se contente de faire une association entre les adresses
et le contenu du fichier, donc on évite une allocation et la recopie de
tous les octets en mémoire ? Et peut-être des appels systèmes pour les
lectures successives de blocs dans le fichier.

Oui, c'est ça. Notons que l'optimisation n'est pas forcément rentable
car jouer sur l'espace d'adressage et provoquer des fautes de page peut
être plus coûteux qu'un appel système bien optimisé.
Le contenu de /proc/$PID/maps laisse entendre que c'est plutôt géré au
niveau du système de fichiers, non ? Il y a des chemins vers les fichiers
dans ce qui s'affiche. À moins que ce ne soit qu'une indication pour s'y
retrouver ?

Ce sont des fichiers qui sont mmapés, ça ne laisse pas grand choix quant
à la manière de l'implémenter.
Du coup une mise à jour de paquets alors que du code s'exécute est assez
dangereuse.

Heureusement que les gestionnaires de paquets suppriment les fichiers
avant de les écraser.
Avatar
Lucas Levrel
Le 27 février 2019, à 10:04, Yliur a écrit :
Au passage, cette histoire de mmap sur des fichiers, il me semble avoir
vu que c'était parfois utilisé comme optimisation de lecture de fichiers
classiques (dont on veut juste lire le contenu) : c'est parce qu'au lieu
d'allouer un tableau d'octets en mémoire et de copier le contenu d'un
fichier dedans on se contente de faire une association entre les adresses
et le contenu du fichier, donc on évite une allocation et la recopie de
tous les octets en mémoire ? Et peut-être des appels systèmes pour les
lectures successives de blocs dans le fichier.

La seule fois où j'ai utilisé un mmap dans un code, ça a chargé
l'intégralité du fichier en mémoire dès l'appel (peut-être même que
l'appel a été bloquant, je ne sais plus), alors que mon but était
évidemment de l'éviter. À mon avis (je ne suis pas informaticien) il y a
tellement de niveaux d'optimisation entre le code source et l'exécution
qu'il est difficile de prévoir, sur la base du source, comment un code va
s'exécuter.
Je crossposte sur fr.comp.lang.c
--
LL
Ἕν οἶδα ὅτι οὐδὲν οἶδα (Σωκράτης)
Avatar
Nicolas George
Lucas Levrel , dans le message
, a écrit :
La seule fois où j'ai utilisé un mmap dans un code, ça a chargé
l'intégralité du fichier en mémoire dès l'appel (peut-être même que
l'appel a été bloquant, je ne sais plus), alors que mon but était
évidemment de l'éviter.

C'est peu crédible. J'aimerais voir le code et la manière de
diagnostiquer.
D'ailleurs, tu dis « peut-être même bloquant », donc ce n'est pas sûr.
Comment évalues-tu que le fichier a été chargé intégralement si ça n'a
pas été bloquant.
Je crossposte sur fr.comp.lang.c

mmap() n'a rien à voir avec le C, fu2 repositionné vers le groupe de
départ, moins inadapté.
Avatar
espie
In article ,
Lucas Levrel wrote:
Le 27 février 2019, à 10:04, Yliur a écrit :
Au passage, cette histoire de mmap sur des fichiers, il me semble avoir
vu que c'était parfois utilisé comme optimisation de lecture de fichiers
classiques (dont on veut juste lire le contenu) : c'est parce qu'au lieu
d'allouer un tableau d'octets en mémoire et de copier le contenu d'un
fichier dedans on se contente de faire une association entre les adresses
et le contenu du fichier, donc on évite une allocation et la recopie de
tous les octets en mémoire ? Et peut-être des appels systèmes pour les
lectures successives de blocs dans le fichier.

La seule fois où j'ai utilisé un mmap dans un code, ça a chargé
l'intégralité du fichier en mémoire dès l'appel (peut-être même que
l'appel a été bloquant, je ne sais plus), alors que mon but était
évidemment de l'éviter. À mon avis (je ne suis pas informaticien) il y a
tellement de niveaux d'optimisation entre le code source et l'exécution
qu'il est difficile de prévoir, sur la base du source, comment un code va
s'exécuter.

Ton avis est faux concernant mmap. Je ne sais pas ce que tu as branlé, mais
à partir du moment où tu utilises mmap, ben c'est l'OS qui s'y colle, donc
c'est raisonnablement prévisible, bien au contraire.
Evidemment que la résolution du nom de fichier est bloquante, et la
réservation de pages dans ton espace mémoire aussi. Pour la lecture à
proprement parler, non pas trop. Mais il faut évidemment que le fichier
fasse une certaine taille pour que ça présente un intérêt.
Il n'y a pas tant de niveaux d'optimisation que ça, et c'est relativement
facile de jeter un oeil au code assembleur en cas de gros doute.
Avatar
Pascal J. Bourguignon
Lucas Levrel writes:
Le 27 février 2019, à 10:04, Yliur a écrit :
Au passage, cette histoire de mmap sur des fichiers, il me semble avoir
vu que c'était parfois utilisé comme optimisation de lecture de fichiers
classiques (dont on veut juste lire le contenu) : c'est parce qu'au lieu
d'allouer un tableau d'octets en mémoire et de copier le contenu d'un
fichier dedans on se contente de faire une association entre les adresses
et le contenu du fichier, donc on évite une allocation et la recopie de
tous les octets en mémoire ? Et peut-être des appels systèmes pour les
lectures successives de blocs dans le fichier.

La seule fois où j'ai utilisé un mmap dans un code, ça a chargé
l'intégralité du fichier en mémoire dès l'appel (peut-être même que
l'appel a été bloquant, je ne sais plus), alors que mon but était
évidemment de l'éviter. À mon avis (je ne suis pas informaticien) il y
a tellement de niveaux d'optimisation entre le code source et
l'exécution qu'il est difficile de prévoir, sur la base du source,
comment un code va s'exécuter.
Je crossposte sur fr.comp.lang.c

mmap ne charge pas l'intégralité du fichier. En fait, il ne charge
absolument rien. Il ne fait que réserver les adresses en mémoire
virtuelle.
Une page du fichier n'est lue (chargée en mémoire) que lorsqu'on y
accède.
--
__Pascal J. Bourguignon__
http://www.informatimago.com
1 2