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

copie lors d'un fork

20 réponses
Avatar
Sylvain SF
Bonjour,

ma question n'a pas son origine dans un point lié à C++ mais
ses conséquentes impactes un code C++.

soit une librairie partagée pouvant contenir de nombreux objets
(le fait qu'ils soient ou non copiable par code n'est pas le point ici).

cette lib. est utilisée par une application, cette liaison puis
l'utilisation de la lib. par cette appli. initialisent des objets
internes de la lib. (par interne je veux dire non public et donc
non connue de l'application externe) et quelques objets publics
(ici "objets" est générique, il signifiera le plus souvent que
l'application externe dispose d'un pointeur vers quelque chose
alloué et initialisé dans la librairie).

supposons maintenant que l'application fasse un fork, créant
un fils ("C") (supposons si besoin que C fasse aussi une
opération d'écriture pour avoir son propre espace mémoire).

ma question:
comment la librairie liée est effectivement forkée ?
dispose-t-elle également de son propre espace mémoire?
ie la représentation mémoire de la lib. lié par C est-elle
différente de celle liée par l'application parent ?
si la réponse est positive, je supposerais que cette
nouvelle copie est alors initialisée comme pour un
premier chargement (appel des constructeurs static
notamment).
si la réponse est négative, ou non définie, merci pour
une synthèse des possibles.

Sylvain.

10 réponses

1 2
Avatar
James Kanze
On May 21, 3:01 am, Sylvain SF wrote:

ma question n'a pas son origine dans un point lié à C++ mais
ses conséquentes impactes un code C++.



soit une librairie partagée pouvant contenir de nombreux
objets (le fait qu'ils soient ou non copiable par code n'est
pas le point ici).



cette lib. est utilisée par une application, cette liaison
puis l'utilisation de la lib. par cette appli. initialisent
des objets internes de la lib. (par interne je veux dire non
public et donc non connue de l'application externe) et
quelques objets publics (ici "objets" est générique, il
signifiera le plus souvent que l'application externe dispose
d'un pointeur vers quelque chose alloué et initialisé dans la
librairie).



supposons maintenant que l'application fasse un fork, créant
un fils ("C") (supposons si besoin que C fasse aussi une
opération d'écriture pour avoir son propre espace mémoire).



ma question:
comment la librairie liée est effectivement forkée ?



C'est une bonne question. La norme C++ ne connaît pas de fork
(ni d'autres processus). La norme Posix ne connaît pas de C++.
Alors, il faut exterpoler pas mal.

Dans la pratique, conceptuellement au moins, le processus fils a
une copie complète de la mémoire du processus père, au moment du
fork. Une copie de la mémoire brute -- le fork ne connaît pas
les objets, au sens C++. Si on appelle « exec » tout de
suite après, cet image tombe à l'eau -- « exec » non plus ne
connaît pas les objets, et n'appellera pas les destructeurs,
etc. Si en revanche l'exécution continue dans le fils sans appel
à exec, le fils va gerer les objets exactement comme s'il les
avait créés lui-même. Si les objets en question gèrent des
ressources externe (fichiers, etc.), ça peut mener à des
surprises (un fichier temporaire qui est effacé deux fois,
etc.).

Selon ce que tu fais, il pourrait être intéressant d'utiliser
_exit(), à la place d'exit(), pour terminer le fils. (La
prochaine version de la norme C++ aura une fonction
« quick_exit », qui est défini de ne pas appeler les
destructeurs des objets à durée de vie statique. Mais en
attendant, dans la pratique, _exit() n'appelle pas les
destructeurs sous les Unix que je connais.)

Aussi, il est probablement préférable d'appeler le fork le plut
tôt possible (avant l'initization du logging, par exemple, si
possible). Et de ne pas retourner de la fonction qui a appelé
fork dans le fils.

dispose-t-elle également de son propre espace mémoire?



Bien sûr. C'est la définition de fork. Pour la définition
complète, voir http://www.unix.org/2008edition/ pour la dernière
édition de la norme Posix. Ou directement à
http://www.opengroup.org/onlinepubs/9699919799/functions/fork.html
pour fork. Il existe aussi des versions antérieurs, à
http://www.unix.org/ ; bien des fois, elles correspondraient
plus aux systèmes que tu utilises. (Solaris 8, par exemple,
n'est que Unix 98.)

Dans la pratique, la plupart des implémentations utilisent en
fait CoW -- beaucoup en negligent même l'allocation de la
mémoire pour pouvoir faire la copie quand il le faut (ce qui
fait que ton application crashe plus tard, sans qu'on sache
pourquoi).

ie la représentation mémoire de la lib. lié par C est-elle
différente de celle liée par l'application parent ?



La mémoire, non. C'est une image exacte. Mais lis bien la
spécification en ce qui concerne d'autres choses (locks, etc. --
et des choses comme le process id, évidemment).

si la réponse est positive, je supposerais que cette
nouvelle copie est alors initialisée comme pour un
premier chargement (appel des constructeurs static
notamment).
si la réponse est négative, ou non définie, merci pour
une synthèse des possibles.



La synthèse, c'est que le processus fils est une copie exacte du
processus père, sauf pour des choses où ce n'est pas une copie
exacte. Voir la norme Posix, et la page de man de ton système
(pour voir où il s'écarte de la norme).

--
James Kanze (GABI Software) email:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
Avatar
Sylvain SF
James Kanze a écrit :
On May 21, 3:01 am, Sylvain SF wrote:

ma question:
comment la librairie liée est effectivement forkée ?



[...]
Dans la pratique, conceptuellement au moins, le processus fils a
une copie complète de la mémoire du processus père, au moment du
fork. Une copie de la mémoire brute -- le fork ne connaît pas
les objets, au sens C++. Si on appelle « exec » tout de
suite après, cet image tombe à l'eau -- « exec » non plus ne
connaît pas les objets, et n'appellera pas les destructeurs,
etc. Si en revanche l'exécution continue dans le fils sans appel
à exec, le fils va gerer les objets exactement comme s'il les
avait créés lui-même. Si les objets en question gèrent des
ressources externe (fichiers, etc.), ça peut mener à des
surprises (un fichier temporaire qui est effacé deux fois,
etc.).



elle gère des ressources externes, des périphériques à accès
exclusif notamment.

Selon ce que tu fais, il pourrait être intéressant d'utiliser
_exit(), à la place d'exit(), pour terminer le fils.



ici, je ne contrôle que la lib. partagée que je développe.
j'anticipe que les applis utilisatrices pourront faire des
fork et je cherche donc à rendre ma librairie "fork savvy"
(autant que faire ce peut).


dispose-t-elle également de son propre espace mémoire?



Bien sûr. C'est la définition de fork. Pour la définition
complète, voir http://www.unix.org/2008edition/ pour la dernière
édition de la norme Posix.



j'ai mal formulé, je voulais dire l'espace mémoire de la lib.
est-il la copie exacte de celui de la lib. telle qu'utilisée par
le parent (donc avec tous ses attributs internes initialisés à
la valeur résultant des appels du parent), ou est-il "vierge"
comme lors d'un chargement initial.

je comprends de tes explications que cet espace est dupliqué
à l'identique (cas 1).

La synthèse, c'est que le processus fils est une copie exacte du
processus père, sauf pour des choses où ce n'est pas une copie
exacte. Voir la norme Posix, et la page de man de ton système
(pour voir où il s'écarte de la norme).



je fais mes premiers tests sur une Fedora mais la lib. pourra
être déployée ailleurs (dont RedHat EE (ça doit ressembler) et
Debian/Ubuntu, pour l'heure j'ignore les subtilités entre ces
distribs).

grand merci pour les références et les explications fournies, cela
balise un peu le terrain des docs à lire et tests à faire.

Sylvain.
Avatar
James Kanze
On May 22, 12:56 am, Sylvain SF wrote:
James Kanze a écrit :
> On May 21, 3:01 am, Sylvain SF wrote:



>> ma question:
>> comment la librairie liée est effectivement forkée ?



> [...]
> Dans la pratique, conceptuellement au moins, le processus
> fils a une copie complète de la mémoire du processus père,
> au moment du fork. Une copie de la mémoire brute -- le fork
> ne connaît pas les objets, au sens C++. Si on appelle « exec
> » tout de suite après, cet image tombe à l'eau -- « exec »
> non plus ne connaît pas les objets, et n'appellera pas les
> destructeurs, etc. Si en revanche l'exécution continue dans
> le fils sans appel à exec, le fils va gerer les objets
> exactement comme s'il les avait créés lui-même. Si les
> objets en question gèrent des ressources externe (fichiers,
> etc.), ça peut mener à des surprises (un fichier temporaire
> qui est effacé deux fois, etc.).



elle gère des ressources externes, des périphériques à accès
exclusif notamment.



Je ne suis pas sûr ce que tu veux dire par « accès exclusif ».
Si tu parles des périphériques spéciales (« block special file  »
ou « character special file », dans la terminologie de Posix),
le plus que tu peux faire, c'est de rejeter une deuxième
ouverture si la périphérique est déjà ouverte. Tu ne peux pas
empêcher un dup() ou dup2(), et dans le cas d'un fork(),
justement, les deux processus vont en avoir accès, parce que les
« file descriptors », c'est une des choses qui sont copiés.

Si l'exclusivité est garantie par des locks, en revanche, les
« file locks » sont une des choses que le fils n'hérite pas.

> Selon ce que tu fais, il pourrait être intéressant
> d'utiliser _exit(), à la place d'exit(), pour terminer le
> fils.



ici, je ne contrôle que la lib. partagée que je développe.
j'anticipe que les applis utilisatrices pourront faire des
fork et je cherche donc à rendre ma librairie "fork savvy"
(autant que faire ce peut).



Si l'application est multi-threaded, il y a pthread_atfork. (À
la rigueur, tu crées un thread à toi juste pour l'utiliser). Tu
t'arranges alors pour que la fonction appelée dans le fils
désactive en quelque sorte des objets, pour qu'il ne peuvent pas
servir dans le processus fils, et qu'ils ne fassent rien dans
leurs destructeurs.

Quoique tu fasses, un peu de documentation pour les utilisateurs
serait indiqués. Qu'ils sachent aussi ce qui se passe lors d'un
fork(), et qu'ils conçoivent leurs applications en conséquence.

--
James Kanze (GABI Software) email:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
Avatar
espie
In article <4a15dc03$0$17759$,
Sylvain SF wrote:
j'ai mal formulé, je voulais dire l'espace mémoire de la lib.
est-il la copie exacte de celui de la lib. telle qu'utilisée par
le parent (donc avec tous ses attributs internes initialisés à
la valeur résultant des appels du parent), ou est-il "vierge"
comme lors d'un chargement initial.

je comprends de tes explications que cet espace est dupliqué
à l'identique (cas 1).



Je me trompe peut-etre, mais j'ai l'impression que tu penses qu'une
bibliotheque a un statut particulier sous Unix.

Il n'en est rien, Unix n'a pas de concept de bibliotheque. Lorsque tu fais
un fork(), tu dupliques l'espace memoire dans sa totalite, code et donnees.
Le systeme optimise les choses un peu (typiquement l'espace de code est en
lecture seule, donc on peut donner "le meme" au deux process), mais il se
contrefout de l'existence de bibliotheques.

Les choses qui changent:
- les elements qui sont geres par le systeme. Typiquement, les file descriptor
qui sont lies a des structures internes au noyau. Les fichiers ouverts sont
"dupliques", mais le systeme sait gerer le bout systeme.
- les primitives de gestion memoire, comme mmap, et les gestions de semaphore
correspondantes. La, tu peux demander que la memoire soit partagee entre
plusieurs process.

Une des difficultes classiques du C, par exemple, c'est que la bibliotheque
standard est independante de fork(), et que ca fout le bazar dans les tampons
des entrees-sorties (d'ou la recommendation habituelle de faire un flush
avant le fork() ).

A l'arrivee:
- soit tu fais du code qui peut etre gene par un fork() et il faut
explicitement que tu geres les choses au niveau systeme.
- soit tu geres au niveau applicatif, ce qui veut dire demander dans la
doc de ta bibliotheque de ne pas fork()er des trucs sensibles, ou fournir
des API supplementaires pour fermer les choses proprement.

En C++, dans les cas ou ca peut etre un souci, tu peux envisager de stocker
un pid dans tes structures sensibles, et de verifier que le pid est toujours
le meme avant de jouer avec tes donnees... c'est un peu lourd, mais ca
devrait au moins te proteger contre les erreurs de programmation evidentes...

Sinon, faudra lire la doc systeme concernant semaphores et zone memoires
partagees si tu veux etre sur que la structure manipulee par deux process
soit effectivement la meme...
Avatar
Sylvain SF
Marc Espie a écrit :

Je me trompe peut-etre, mais j'ai l'impression que tu penses qu'une
bibliotheque a un statut particulier sous Unix.



particulier par rapport à windows ?
je sais que fork n'existe pas pour une appli windows, donc la
question (comment la lib. est forkée) ne se pose pas.

j'ai parlé de librairie partagée (foo.so), je ne sais pas si tu
emploies bibliothèque dans le même sens.

Marc Espie a écrit :
Il n'en est rien, Unix n'a pas de concept de bibliotheque. Lorsque tu fais
un fork(), tu dupliques l'espace memoire dans sa totalite, code et donnees.
Le systeme optimise les choses un peu (typiquement l'espace de code est en
lecture seule, donc on peut donner "le meme" au deux process), mais il se
contrefout de l'existence de bibliotheques.



que le système s'en fiche est son problème, que ma librairie puisse
partir dans le décor si 2 process (parent et fils) l'utilisent en
même temps est mon problème; si le système appelait, si elle existe,
une fonction C (de prototype normé) de la lib. suite au fork, cela
m'aiderait, mais se contrefoutre doit signifier que rien de tel
n'existe.

Marc Espie a écrit :
Les choses qui changent:



entre quoi et quoi ? linux vs windows? ou module principal vs librairie?

Marc Espie a écrit :
- les elements qui sont geres par le systeme. Typiquement, les file descriptor
qui sont lies a des structures internes au noyau. Les fichiers ouverts sont
"dupliques", mais le systeme sait gerer le bout systeme.
- les primitives de gestion memoire, comme mmap, et les gestions de semaphore
correspondantes. La, tu peux demander que la memoire soit partagee entre
plusieurs process.



yep, j'ai bien lu que certains descripteurs sont dupliqués d'autres non.
reste à affiner et utiliser les bons.

Marc Espie a écrit :
Une des difficultes classiques du C, par exemple, c'est que la bibliotheque
standard est independante de fork(), et que ca fout le bazar dans les tampons
des entrees-sorties (d'ou la recommendation habituelle de faire un flush
avant le fork() ).



mon pb ne concerne pas (directement) des E/S ou fonctions standards,
mais des accès exclusif (par un seul code à la fois et par des méthodes
bloquantes) à du hard. un moyen bébête de prevenir l'utilisation par
2 applis et d'avoir un fichier lock mais si le fork aboutit au fait
que la 2nde lib. pense qu'elle dispose du lock, je ne règle pas mon pb.

Marc Espie a écrit :
A l'arrivee:
- soit tu fais du code qui peut etre gene par un fork() et il faut
explicitement que tu geres les choses au niveau systeme.



avec un patch système ou en gérant dans ma lib. des notions systèmes?

Marc Espie a écrit :
- soit tu geres au niveau applicatif, ce qui veut dire demander dans la
doc de ta bibliotheque de ne pas fork()er des trucs sensibles, ou fournir
des API supplementaires pour fermer les choses proprement.



la doc. usuelle pour ce type de lib. est de dire que le comportement
est indéfini; je souhaite voir s'il m'est possible de dépasser cette
indétermination.

En C++, dans les cas ou ca peut etre un souci, tu peux envisager de stocker
un pid dans tes structures sensibles, et de verifier que le pid est toujours
le meme avant de jouer avec tes donnees... c'est un peu lourd, mais ca
devrait au moins te proteger contre les erreurs de programmation evidentes...



c'est une bonne idée.

Sinon, faudra lire la doc systeme concernant semaphores et zone memoires
partagees si tu veux etre sur que la structure manipulee par deux process
soit effectivement la meme...



je ne suis pas dans cette approche (pour l'instant), la lib. est
à usage d'un client (applicatif) unique, je pourrais partir sur
un service (daemon) qui accepterait N connexions de N instances
d'une lib. ne définissant que les APIs, mais ce n'est pas retenu
pour la version en cours.

Sylvain.
Avatar
espie
In article <4a16f2e5$0$17769$,
Sylvain SF wrote:
Marc Espie a écrit :

Je me trompe peut-etre, mais j'ai l'impression que tu penses qu'une
bibliotheque a un statut particulier sous Unix.



particulier par rapport à windows ?
je sais que fork n'existe pas pour une appli windows, donc la
question (comment la lib. est forkée) ne se pose pas.



Non, bien sur que non.

j'ai parlé de librairie partagée (foo.so), je ne sais pas si tu
emploies bibliothèque dans le même sens.



Librairie partagee, c'est un anglicisme. Le terme francais est "bibliotheque".
Pour les "bibliotheques partagees", en fait, c'est plus le concept fonctionnel
qui a un sens, les systemes auxquels tu auras affaire aujourd'hui (cote Unix)
utilisent pratiquement tous ELF, et elf n'a qu'un concept de "loadable module".
C'est avec ca que tu fais tes bibliotheques partagees, et tes "plugins".

A part au chargement, Unix s'en fout: c'est soit un dlopen() explicite, soit
ton ld.so qui s'en charge. Il peut y avoir des constructeurs (c'est d'ailleurs
generalement l'horreur pour que ca marche dans le bon ordre), mais il n'y
a rien a voir cote fork.



Marc Espie a écrit :
Il n'en est rien, Unix n'a pas de concept de bibliotheque. Lorsque tu fais
un fork(), tu dupliques l'espace memoire dans sa totalite, code et donnees.
Le systeme optimise les choses un peu (typiquement l'espace de code est en
lecture seule, donc on peut donner "le meme" au deux process), mais il se
contrefout de l'existence de bibliotheques.



que le système s'en fiche est son problème, que ma librairie puisse
partir dans le décor si 2 process (parent et fils) l'utilisent en
même temps est mon problème; si le système appelait, si elle existe,
une fonction C (de prototype normé) de la lib. suite au fork, cela
m'aiderait, mais se contrefoutre doit signifier que rien de tel
n'existe.



Ben ouais. Ca au moins tu as compris. ;-)

Marc Espie a écrit :
Les choses qui changent:



entre quoi et quoi ? linux vs windows? ou module principal vs librairie?



Entre process pere et fils.


Marc Espie a écrit :
- les elements qui sont geres par le systeme. Typiquement, les file descriptor
qui sont lies a des structures internes au noyau. Les fichiers ouverts sont
"dupliques", mais le systeme sait gerer le bout systeme.
- les primitives de gestion memoire, comme mmap, et les gestions de semaphore
correspondantes. La, tu peux demander que la memoire soit partagee entre
plusieurs process.



yep, j'ai bien lu que certains descripteurs sont dupliqués d'autres non.
reste à affiner et utiliser les bons.



Non, les descripteurs de fichiers sont toujours dupliques, c'est la
signification qui peut changer selon le type de descripteur (typiquement,
la position de la tete de lecture/ecriture, par exemple).

Marc Espie a écrit :
Une des difficultes classiques du C, par exemple, c'est que la bibliotheque
standard est independante de fork(), et que ca fout le bazar dans les tampons
des entrees-sorties (d'ou la recommendation habituelle de faire un flush
avant le fork() ).



mon pb ne concerne pas (directement) des E/S ou fonctions standards,
mais des accès exclusif (par un seul code à la fois et par des méthodes
bloquantes) à du hard. un moyen bébête de prevenir l'utilisation par
2 applis et d'avoir un fichier lock mais si le fork aboutit au fait
que la 2nde lib. pense qu'elle dispose du lock, je ne règle pas mon pb.



Sans vouloir etre mechant, j'ai l'impression que ton probleme est que tu
veux faire les choses vite sans comprendre le fonctionnement de ton systeme,
ce qui est excessivement casse-gueule dans le cas considere. C'est pourquoi
je me suis base sur des exemples classiques et connus histoire de pouvoir
t'expliquer des choses sans vouloir reprendre toutes les bases.


Marc Espie a écrit :
A l'arrivee:
- soit tu fais du code qui peut etre gene par un fork() et il faut
explicitement que tu geres les choses au niveau systeme.



avec un patch système ou en gérant dans ma lib. des notions systèmes?



Laisse tomber l'idee du patch systeme. Deja, ca va etre tres peu portable.
En plus, il faut bien connaitre le systeme. Et pour finir, il est probable
que tes "clients" n'en voudront pas.

Non, juste apprendre et utiliser les appels systemes qui gerent la memoire
partagee et les semaphores, et voir comment tu peux t'en servir dans le
cas qui te concerne...
Avatar
Sylvain SF
Marc Espie a écrit :

A part au chargement, Unix s'en fout: c'est soit un dlopen() explicite, soit
ton ld.so qui s'en charge.



dlopen() ici, l'appli. utilisatrice ne me connaît pas a priori.

Non, les descripteurs de fichiers sont toujours dupliques [...]



c'est ce que je disais.

Sans vouloir etre mechant, j'ai l'impression que ton probleme est que tu
veux faire les choses vite sans comprendre le fonctionnement de ton systeme,
ce qui est excessivement casse-gueule dans le cas considere.



vite ? pourquoi donc ? je suis payé à l'heure.
il peut m'arriver de coder vite, mais je réfléchis très lentement avant
(et cela n'a sûrement rien d'original).

C'est pourquoi
je me suis base sur des exemples classiques et connus histoire de pouvoir
t'expliquer des choses sans vouloir reprendre toutes les bases.



zut, il me manque ces bases, je le crains.

Laisse tomber l'idee du patch systeme. [...]



je n'en veux en aucun cas, je demandais confirmation qu'il s'agissait
de cela pour en exclure l'idée.

Non, juste apprendre et utiliser les appels systemes qui gerent la memoire
partagee et les semaphores, et voir comment tu peux t'en servir dans le
cas qui te concerne...



oui, ... euh, comment dit-on "GlobalAlloc" en POSIX dans le texte ?

Sylvain.
Avatar
espie
In article <4a1714e0$0$17742$,
Sylvain SF wrote:
oui, ... euh, comment dit-on "GlobalAlloc" en POSIX dans le texte ?



La, j'espere que quelqu'un d'autre pourra t'aider, parce que je ne parle
pas windows...
Avatar
Sylvain SF
Marc Espie a écrit :
In article <4a1714e0$0$17742$, Sylvain SF
wrote:
oui, ... euh, comment dit-on "GlobalAlloc" en POSIX dans le texte ?



La, j'espere que quelqu'un d'autre pourra t'aider, parce que je ne
parle pas windows...



ben, je posais la question à quelqu'un connaissant POSIX, pas windows
justement -- ok, "GlobalAlloc" alloue un bloc (à visibilité) global(e)
cela ne demande pas une forte connaissance de windows et on parlait
de cela:

Marc Espie a écrit :
utiliser les appels systemes qui gerent la memoire partagee



je suis tombé sur un package OSSP mais je ne connais pas son statut,
d'où ma question, avec un point d'entrée je saurais ce que je cherche.

Sylvain.
Avatar
Marc
Sylvain SF wrote:

oui, ... euh, comment dit-on "GlobalAlloc" en POSIX dans le texte ?



En C++ on peut essayer boost.interprocess (je ne l'ai jamais utilisé
personnellement).
1 2