Le fork()
et vfork()
les wrappers dans la glibc sont implémentés via le clone()
appel système. Pour mieux comprendre la relation entre fork()
et clone()
, nous devons considérer la relation entre les processus et les threads sous Linux.
Traditionnellement, fork()
dupliquerait toutes les ressources détenues par le processus parent et affecterait la copie au processus enfant. Cette approche entraîne une surcharge considérable, qui peut être inutile si l'enfant appelle immédiatement exec()
. Sous Linux, fork()
utilise la copie sur écriture pages pour retarder ou éviter complètement de copier les données qui peuvent être partagées entre les processus parent et enfant. Ainsi, le seul surcoût encouru lors d'un fork()
normal est la copie des tables de pages du parent et l'affectation d'une structure de descripteur de processus unique, task_struct
, pour l'enfant.
Linux adopte également une approche exceptionnelle des threads. Sous Linux, les threads sont simplement des processus ordinaires qui partagent certaines ressources avec d'autres processus. Il s'agit d'une approche radicalement différente des threads par rapport à d'autres systèmes d'exploitation tels que Windows ou Solaris, où les processus et les threads sont des types de bêtes entièrement différents. Sous Linux, chaque thread a un task_struct
ordinaire qui se trouve être configuré de telle sorte qu'il partage certaines ressources, comme un espace d'adressage, avec le processus parent.
Le flags
paramètre du clone()
L'appel système inclut un ensemble d'indicateurs qui indiquent quelles ressources, le cas échéant, les processus parent et enfant doivent partager. Les processus et les threads sont tous deux créés via clone()
, la seule différence est le jeu d'indicateurs passé à clone()
.
Un fork()
normal pourrait être implémenté comme :
clone(SIGCHLD, 0);
Cela crée une tâche qui ne partage aucune ressource avec son parent et est configurée pour envoyer le SIGCHLD
signal de terminaison au parent lorsqu'il quitte.
En revanche, une tâche qui partage l'espace d'adressage, les ressources du système de fichiers, les descripteurs de fichiers et les gestionnaires de signaux avec le parent, en d'autres termes un thread , peut être créé avec :
clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0);
vfork()
est à son tour implémenté via un CLONE_VFORK
séparé flag, qui mettra le processus parent en veille jusqu'à ce que le processus enfant le réveille via un signal. L'enfant sera le seul thread d'exécution dans l'espace de noms du parent, jusqu'à ce qu'il appelle exec()
ou sorties. L'enfant n'est pas autorisé à écrire dans la mémoire. Le clone()
correspondant l'appel pourrait être le suivant :
clone(CLONE_VFORK | CLONE_VM | SIGCHLD, 0)
L'implémentation de sys_clone()
est spécifique à l'architecture, mais le gros du travail se passe en do_fork()
défini dans kernel/fork.c
. Cette fonction appelle le clone_process()
statique , qui crée un nouveau processus en tant que copie du parent, mais ne le démarre pas encore. clone_process()
copie les registres, attribue un PID à la nouvelle tâche et duplique ou partage les parties appropriées de l'environnement de processus comme spécifié par le clone flags
. Quand clone_process()
renvoie, do_clone()
réveillera le processus nouvellement créé et planifiera son exécution.
Le composant responsable de la traduction des fonctions d'appel système de l'espace utilisateur en appels système du noyau sous Linux est la libc. Dans GLibC, la bibliothèque NPTL redirige ceci vers le clone(2)
appel système.