Lorsqu'un processus enfant créé par
vfork()
appelleexec()
, neexec()
modifier l'espace d'adressage du processus parent, en chargeant le nouveau programme ?
Non, exec()
fournit un nouvel espace d'adressage pour le nouveau programme ; il ne modifie pas l'espace d'adressage parent. Voir par exemple la discussion du exec
fonctions dans POSIX et Linux execve()
page de manuel.
Lorsqu'un processus enfant créé par vfork() appelle exit(), exit() ne modifie-t-il pas l'espace d'adressage du processus parent lors de la terminaison de l'enfant ?
exit()
ordinaire pourrait - il exécute les crochets de sortie installés par le programme en cours d'exécution (y compris ses bibliothèques). vfork()
est plus restrictif ; ainsi, sous Linux, il impose l'utilisation de _exit()
ce qui ne le fait pas appeler les fonctions de nettoyage de la bibliothèque C.
vfork()
s'est avéré assez difficile à obtenir correctement; il a été supprimé dans les versions actuelles de la norme POSIX, et posix_spawn()
devrait être utilisé à la place.
Cependant, à moins que vous vraiment savoir ce que vous faites, vous ne devriez pas utilisez soit vfork()
ou posix_spawn()
; s'en tenir au bon vieux fork()
et exec()
.
La page de manuel Linux liée ci-dessus fournit plus de contexte :
Cependant, au mauvais vieux temps, un
fork(2)
nécessiterait de faire une copie complète de l'espace de données de l'appelant, souvent inutilement, car généralement immédiatement après unexec(3)
est fait. Ainsi, pour plus d'efficacité, BSD a introduit levfork()
appel système, qui n'a pas entièrement copié l'espace d'adressage du processus parent, mais a emprunté la mémoire et le fil de contrôle du parent jusqu'à un appel àexecve(2)
ou une sortie s'est produite. Le processus parent a été suspendu pendant que l'enfant utilisait ses ressources. L'utilisation devfork()
était délicat :par exemple, ne pas modifier les données dans le processus parent dépendait de la connaissance des variables contenues dans un registre.
Lorsque vous appelez le vfork()
, un nouveau processus est créé et ce nouveau processus emprunte l'image de processus du processus parent à l'exception de la pile. Le processus enfant reçoit sa propre nouvelle étoile de pile mais ne permet pas de return
de la fonction qui a appelé vfork()
.
Pendant que l'enfant est en cours d'exécution, le processus parent est bloqué, car l'enfant a emprunté l'espace d'adressage du parent.
Quoi que vous fassiez, tout ce qui accède à la pile ne modifie que la pile privée de l'enfant. Si vous modifiez par contre les données globales, cela modifie les données communes et affecte donc également le parent.
Les éléments qui modifient les données globales sont par exemple :
-
appeler malloc() ou free()
-
en utilisant stdio
-
modifier les paramètres du signal
-
modifier les variables qui ne sont pas locales à la fonction qui a appelé
vfork()
. -
...
Une fois que vous avez appelé le _exit()
(important, ne jamais appeler le exit()
), l'enfant est supprimé et le contrôle est rendu au parent.
Si vous appelez une fonction depuis le exec*()
famille, un nouvel espace d'adressage est créé avec un nouveau code de programme, de nouvelles données et une partie de la pile du parent (voir ci-dessous). Une fois que c'est prêt, l'enfant n'emprunte plus l'espace d'adressage de l'enfant, mais utilise son propre espace d'adressage.
Le contrôle est rendu au parent, car son espace d'adressage n'est plus utilisé par un autre processus.
Important :Sous Linux, il n'y a pas de véritable vfork()
la mise en oeuvre. Linux implémente plutôt vfork()
basé sur la copie sur écriture fork()
concept introduit par SunOS-4.0 en 1988. Afin de faire croire aux utilisateurs qu'ils utilisent vfork()
, Linux configure simplement les données partagées et suspend le parent alors que l'enfant n'a pas appelé _exit()
ou l'un des exec*()
fonctions.
Linux ne profite donc pas du fait qu'un vrai vfork()
n'a pas besoin de configurer une description d'espace d'adressage pour l'enfant dans le noyau. Cela se traduit par un vfork()
qui n'est pas plus rapide que fork()
. Sur les systèmes qui implémentent un vrai vfork()
, il est généralement 3 fois plus rapide que fork()
et affecte les performances des shells qui utilisent vfork()
- ksh93
, le récent Bourne Shell
et csh
.
La raison pour laquelle vous ne devriez jamais appeler le exit()
du vfork()
ed enfant est que exit()
vide stdio au cas où il y aurait des données non vidées à partir du moment avant d'appeler vfork()
. Cela pourrait entraîner des résultats étranges.
Au fait :posix_spawn()
est implémenté au-dessus de vfork()
, donc vfork()
ne va pas être supprimé du système d'exploitation. Il a été mentionné que Linux n'utilise pas vfork()
pour posix_spawn()
.
Pour la pile, il y a peu de documentation, voici ce que dit la page de manuel de Solaris :
The vfork() and vforkx() functions can normally be used the
same way as fork() and forkx(), respectively. The calling
procedure, however, should not return while running in the
child's context, since the eventual return from vfork() or
vforkx() in the parent would be to a stack frame that no
longer exists.
Ainsi, l'implémentation peut faire ce qu'elle veut. L'implémentation Solaris utilise la mémoire partagée pour le cadre de pile de la fonction appelant vfork()
. Aucune implémentation n'accorde l'accès aux anciennes parties de la pile depuis le parent.