vfork(2) System Calls Manual vfork(2)

vfork - Créer un processus enfant et bloquer le parent

Bibliothèque C standard (libc, -lc)

#include <unistd.h>
pid_t vfork(void);
Exigences de macros de test de fonctionnalités pour la glibc (consulter feature_test_macros(7)) :

vfork() :

    Depuis la glibc 2.12 :
        (_XOPEN_SOURCE >= 500) && ! (_POSIX_C_SOURCE >= 200809L)
            || /* Depuis la glibc 2.19 : */ _DEFAULT_SOURCE
            || /* glibc <= 2.19 : */ _BSD_SOURCE
    Avant la glibc 2.12 :
        _BSD_SOURCE || _XOPEN_SOURCE >= 500

(D'après POSIX.1). La routine vfork() a le même effet que fork(2), sauf que le comportement est indéfini si le processus créé par vfork() effectue l'une des actions suivantes avant d'appeler avec succès _exit(2) ou une routine de la famille exec(3) : modification d'une donnée autre que la variable de type pid_t stockant le retour de vfork(), revenir de la fonction dans laquelle vfork() a été invoqué, appel d'une autre fonction.

vfork(), tout comme fork(2), crée un processus enfant à partir du processus appelant. Pour plus de détails sur les valeurs renvoyées et les erreurs possibles, consultez fork(2).

vfork() est conçu comme un cas particulier de clone(2). Il sert à créer un nouveau processus sans effectuer de copie de la table des pages mémoire du processus parent. Cela peut être utile dans des applications nécessitant une grande rapidité d'exécution, si l'enfant doit invoquer immédiatement un appel execve(2).

vfork() diffère de fork(2) en ce que le thread appelant reste suspendu jusqu'à ce que l'enfant se termine (soit normalement, en appelant _exit(2), soit de façon anormale après l'envoi d'un signal fatal) ou qu'il appelle execve(2). Jusqu'à ce point, l'enfant partage toute la mémoire avec son parent, y compris la pile. Le processus enfant ne doit pas revenir de la fonction en cours, ni appeler exit(3) (ce qui aurait pour effet d'appeler les gestionnaires de sortie établis par le processus parent et de vider les tampons stdio(3) du parent), mais appeler à _exit(2).

Comme avec fork(2), le processus enfant créé par vfork() hérite des copies de plusieurs attributs du processus appelant (par exemple les descripteurs de fichiers, les dispositions des signaux et le répertoire de travail actuel) ; l'appel vfork() diffère seulement par le traitement de l'espace d'adressage virtuel, comme décrit ci-dessus.

Les signaux pour le processus parent sont délivrés après que l'enfant libère la mémoire du parent (c'est-à-dire après que l'enfant se termine ou qu'il appelle execve(2)).

Sous Linux, fork(2) est implémenté en utilisant un mécanisme de copie en écriture, ainsi ses seuls coûts sont le temps et la mémoire nécessaire pour dupliquer la table des pages mémoire du processus parent, et créer une seulestructure de tâche pour l'enfant. Toutefois, jadis fork(2) nécessitait malheureusement une copie complète de l'espace de données du parent, souvent inutilement, car un appel exec(3) est souvent réalisé immédiatement par l'enfant. Pour améliorer les performances, BSD a introduit un appel système vfork() qui ne copie pas l'espace d'adressage du parent, mais emprunte au parent son espace d'adressage et son fil de contrôle jusqu'à un appel à execve(2) ou qu'une sortie survienne. Le processus parent était suspendu tant que l'enfant utilisait les ressources. L'utilisation de vfork() était loin d'être facile, car, pour éviter de modifier les données du processus parent, il fallait être capable de déterminer quelles variables se trouvaient dans des registres du processeur.

Les exigences que les normes apportent sur vfork() sont plus relâchées que celles sur fork(2), ainsi il est possible d'avoir une implémentation conforme où les deux appels sont synonymes. En particulier, un programmeur ne doit pas s'appuyer sur le fait que le parent reste bloqué jusqu'à ce que l'enfant se termine ou appelle execve(2), ni sur le comportement par rapport à la mémoire partagée.

Certaines personnes considèrent la sémantique de vfork() comme une verrue architecturale, et la page de manuel de 4.2BSD indique que « cet appel système sera supprimé quand des mécanismes de partage appropriés seront implémentés. Il ne faut pas essayer de tirer profit du partage mémoire induit par vfork, car dans ce cas il serait rendu synonyme de fork(2) ». Cependant, même si le matériel de gestion mémoire moderne a diminué la différence de performances entre fork(2) et vfork(), il existe diverses raisons pour lesquelles Linux et d'autres systèmes ont conservé vfork() :

  • Certaines applications de haute performance ont besoin du petit gain apporté par vfork().
  • vfork() peut être implémenté sur des systèmes sans unité de gestion mémoire (MMU, pour « memory-management unit »), mais fork(2) ne peut pas être implémenté sur de tels systèmes (POSIX.1-2008 a supprimé vfork() de la norme ; la raison invoquée par POSIX pour la fonction posix_spawn(3) note que cette fonction, qui fournit une fonctionnalité équivalente à fork(2)+ exec(3), est conçue pour être implémentable sur des systèmes sans MMU).
  • Sur les systèmes où la mémoire est restreinte, vfork évite le besoin d'allouer de la mémoire temporairement (voir la description de /proc/sys/vm/overcommit_memory dans proc(5)) afin d'exécuter un nouveau programme. Cela peut être particulièrement bénéfique lorsqu'un grand processus parent souhaite exécuter un petit programme d'assistance dans un processus enfant. Par contraste, l'utilisation de fork(2) dans ce scénario nécessite soit l'allocation d'une quantité de mémoire égale à la taille du processus parent (si la gestion stricte du dépassement est en cours) soit un dépassement de mémoire avec le risque qu'un processus soit terminé par la mise à mort sur mémoire saturée (OOM killer).

Les gestionnaires enregistrés avec pthread_atfork(3) ne sont pas appelés lorsqu'un programme multithreadé utilisant la bibliothèque de threads NPTL appelle vfork(). En revanche ces gestionnaires sont appelés si le programme utilise la bibliothèque LinuxThreads. (Consultez pthreads(7) pour une description des bibliothèques de threads pour Linux.)

Un appel à vfork() est équivalent à appeler clone(2) avec flags valant :


 CLONE_VM | CLONE_VFORK | SIGCHLD

Aucun.

4.3BSD, POSIX.1-2001 (mais la déclare obsolète). POSIX.1-2008 supprime la spécification de vfork().

L'appel système vfork() est apparu dans 3.0BSD. Dans 4.4BSD, il est devenu synonyme de fork(2), mais NetBSD l'a réintroduit à nouveau (consultez http://www.netbsd.org/Documentation/kernel/vfork.html ). Sous Linux, il a été l'équivalent de fork(2) jusqu'à Linux 2.2.0-pre-6. Depuis Linux 2.2.0-pre-9 (sur i386, un peu plus tard sur d'autres architectures), il s'agit d'un appel système indépendant. La prise en charge a été introduite dans la glibc 2.0.112.

Le processus enfant devrait veiller à ne pas modifier la mémoire d'une manière inattendue, dans la mesure où les modifications seront vues par le processus parent une fois que l'enfant s'est achevé ou exécute un autre programme. À cet égard, les gestionnaires de signaux peuvent être particulièrement problématiques : si un gestionnaire de signal qui est invoqué dans l'enfant d'un vfork() modifie la mémoire, ces modifications peuvent aboutir à une inconsistance de l'état du processus du point de vue du processus parent (par exemple, les modifications de la mémoire pourraient être visibles dans le parent, mais pas les modifications de l'état des descripteurs de fichiers ouverts).

Lorsque vfork() est appelé dans un processus multithreadé, seul le thread appelant est suspendu jusqu'à ce que l'enfant s'achève ou exécute un nouveau programme. Cela signifie que l'enfant partage un espace d'adresses avec un autre code en cours d'exécution. Cela peut être dangereux si un autre thread dans le processus parent modifie son identifiant (avec setuid(2) ou une autre commande similaire), dans la mesure où il y a alors deux processus avec différents niveaux de privilèges exécutés dans le même espace d'adresses. Comme exemple de risque, imaginez qu'un programme multithreadé exécuté en tant que superutilisateur crée un enfant avec vfork(). Après le vfork(), un thread dans le processus parent transfère le processus à un utilisateur non privilégié afin d'exécuter du code non sûr (par exemple, peut-être au moyen d'un greffon ouvert avec dlopen(3)). Dans ce cas, des attaques sont possibles où le processus parent utilise mmap(2) pour projeter en mémoire du code qui sera exécuté par le processus enfant privilégié.

Les détails de la gestion des signaux sont compliqués et varient suivant les systèmes. La page de manuel BSD indique : « Pour éviter une possible situation d'interblocage, les processus qui sont des enfants au milieu d'un vfork() ne reçoivent jamais le signal SIGTTOU ou SIGTTIN ; des sorties et des ioctl sont autorisés, mais des tentatives de lecture donneront une indication de fin de fichier. »

clone(2), execve(2), _exit(2), fork(2), unshare(2), wait(2)

La traduction française de cette page de manuel a été créée par Christophe Blaess https://www.blaess.fr/christophe/, Stéphan Rafin <stephan.rafin@laposte.net>, Thierry Vignaud <tvignaud@mandriva.com>, François Micaux, Alain Portal <aportal@univ-montp2.fr>, Jean-Philippe Guérard <fevrier@tigreraye.org>, Jean-Luc Coulon (f5ibh) <jean-luc.coulon@wanadoo.fr>, Julien Cristau <jcristau@debian.org>, Thomas Huriaux <thomas.huriaux@gmail.com>, Nicolas François <nicolas.francois@centraliens.net>, Florentin Duneau <fduneau@gmail.com>, Simon Paillard <simon.paillard@resel.enst-bretagne.fr>, Denis Barbier <barbier@debian.org>, David Prévot <david@tilapin.org> et Jean-Pierre Giraud <jean-pierregiraud@neuf.fr>

Cette traduction est une documentation libre ; veuillez vous reporter à la GNU General Public License version 3 concernant les conditions de copie et de distribution. Il n'y a aucune RESPONSABILITÉ LÉGALE.

Si vous découvrez un bogue dans la traduction de cette page de manuel, veuillez envoyer un message à debian-l10n-french@lists.debian.org.

2 mai 2024 Pages du manuel de Linux 6.8