242 votes

La différence entre fork(), vfork(), exec() et clone()

Je cherchais à trouver la différence entre ces quatre appels sur Google et je m'attendais à ce qu'il y ait une énorme quantité d'informations à ce sujet, mais il n'y avait vraiment aucune comparaison solide entre les quatre appels.

J'ai essayé de compiler une sorte d'aperçu de base des différences entre ces appels système et voici ce que j'ai obtenu. Toutes ces informations sont-elles correctes ou ai-je oublié quelque chose d'important ?

Fork : L'appel fork crée essentiellement un double du processus actuel, identique en presque tous points (tout n'est pas copié, par exemple, les limites de ressources dans certaines implémentations, mais l'idée est de créer une copie aussi proche que possible).

Le nouveau processus (enfant) obtient un ID de processus (PID) différent et a le PID de l'ancien processus (parent) comme son PID parent (PPID). Comme les deux processus exécutent maintenant exactement le même code, ils peuvent dire lequel est lequel par le code de retour de fork - l'enfant obtient 0, le parent obtient le PID de l'enfant. Tout ceci, bien sûr, en supposant que l'appel fork fonctionne - si ce n'est pas le cas, aucun enfant n'est créé et le parent reçoit un code d'erreur.

Vfork : La différence fondamentale entre vfork et fork est que lorsqu'un nouveau processus est créé avec vfork(), le processus parent est temporairement suspendu, et le processus enfant pourrait emprunter l'espace d'adressage du parent. Cet étrange état de fait se poursuit jusqu'à ce que le processus enfant quitte, ou appelle execve(), auquel cas le processus parent continue. parent continue.

Cela signifie que le processus enfant d'un vfork() doit faire attention à ne pas modifier de manière inattendue les variables du processus parent. En particulier, le processus enfant ne doit pas retourner de la fonction contenant l'appel vfork(), et il ne doit pas appeler exit() (s'il doit sortir, il devrait utiliser _exit() ; en fait, c'est aussi vrai pour l'enfant d'un fork() normal).

Exec : L'appel exec est un moyen de remplacer l'ensemble du processus actuel par un nouveau programme. Il charge le programme dans l'espace du processus actuel et l'exécute à partir du point d'entrée. exec() remplace le processus actuel par un exécutable pointé par la fonction. Le contrôle ne revient jamais au programme d'origine, sauf en cas d'erreur de exec().

Clone : Clone, comme fork, crée un nouveau processus. Contrairement à fork, ces appels permettent au processus enfant de partager des parties de son contexte d'exécution avec le processus appelant, comme l'espace mémoire, la table des descripteurs de fichiers et la table des gestionnaires de signaux.

Lorsque le processus enfant est créé avec clone, il exécute la fonction application fn(arg). (Cela diffère de for, où l'exécution se poursuit dans le processus fils à partir du point d'appel de fork). L'argument fn est un pointeur vers une fonction qui est appelée par le processus enfant au début de son exécution. L'argument arg est passé à la fonction fn.

Lorsque l'application de la fonction fn(arg) revient, le processus enfant se termine. Le nombre entier renvoyé par fn est le code de sortie du processus fils. Le processus enfant peut aussi se terminer explicitement en appelant exit(2) ou après avoir reçu un signal fatal.

Formulaire d'obtention d'informations :

Merci d'avoir pris le temps de lire ceci ! :)

2 votes

Pourquoi vfork ne doit-il pas appeler exit() ? Ou ne pas retourner ? Est-ce que exit() n'utilise pas simplement _exit() ? J'essaie également de comprendre :)

2 votes

@Gnuey : parce qu'il est potentiellement (s'il est mis en oeuvre différemment de fork() ce qu'il est dans Linux, et probablement dans tous les BSDs) empruntant l'espace d'adressage de son parent. Tout ce qu'il fait, à part appeler execve() o _exit() a un grand potentiel pour perturber le parent. En particulier, exit() appelle atexit() et d'autres "finaliseurs", par exemple : il vide les flux stdio. Le retour d'un vfork() enfant pourrait potentiellement (même mise en garde que précédemment) perturber la pile du parent.

0 votes

Je me demande ce qu'il advient des threads du processus parent. Sont-ils tous clonés ou seulement le thread qui appelle la fonction fork syscall ?

189voto

Javier Points 33134
  • vfork() est une optimisation obsolète. Avant une bonne gestion de la mémoire, fork() faisait une copie complète de la mémoire du parent, donc c'était assez coûteux. puisque dans de nombreux cas, une fork() a été suivi par exec() qui abandonne la carte mémoire actuelle et en crée une nouvelle, c'était une dépense inutile. De nos jours, fork() ne copie pas la mémoire ; elle est simplement définie comme "copy on write", donc fork() + exec() est tout aussi efficace que vfork() + exec() .

  • clone() est le syscall utilisé par fork() . avec certains paramètres, il crée un nouveau processus, avec d'autres, il crée un thread. la différence entre eux est juste de savoir quelles structures de données (espace mémoire, état du processeur, pile, PID, fichiers ouverts, etc) sont partagées ou non.

13 votes

2 votes

29 votes

vfork évite d'avoir à mobiliser temporairement beaucoup plus de mémoire pour pouvoir exécuter des opérations. exec et il est toujours plus efficace que fork même si ce n'est pas à un degré aussi élevé. Ainsi, on peut éviter d'avoir à surcommettre de la mémoire juste pour qu'un gros programme puisse engendrer un processus enfant. Il ne s'agit donc pas d'une simple amélioration des performances, mais d'un moyen de rendre l'opération tout à fait réalisable.

90voto

ninjalj Points 22026
  • execve() remplace l'image exécutable actuelle par une autre image chargée à partir d'un fichier exécutable.
  • fork() crée un processus enfant.
  • vfork() est une version historique optimisée de fork() Il est destiné à être utilisé lorsque execve() est appelé directement après fork() . Il s'est avéré qu'elle fonctionne bien dans les systèmes non-MMU (dans lesquels fork() ne peut pas travailler de manière efficace) et lorsque fork() de processus avec une empreinte mémoire énorme pour exécuter un petit programme (pensez à Java) Runtime.exec() ). POSIX a normalisé le posix_spawn() pour remplacer ces deux dernières utilisations plus modernes de vfork() .
  • posix_spawn() fait l'équivalent d'un fork()/execve() et permet également de jongler avec les fd entre les deux. Il est censé remplacer fork()/execve() principalement pour les plateformes non-MMU.
  • pthread_create() crée un nouveau fil.
  • clone() est un appel spécifique à Linux, qui peut être utilisé pour implémenter n'importe quoi de fork() à pthread_create() . Il donne beaucoup de contrôle. Inspiré par rfork() .
  • rfork() est un appel spécifique à Plan-9. C'est censé être un appel générique, permettant plusieurs degrés de partage, entre processus complets et threads.

2 votes

Merci d'avoir ajouté plus d'informations que ce qui était demandé, cela m'a permis de gagner du temps.

6 votes

Le plan 9 est tellement aguicheur.

1 votes

Pour ceux qui ne se souviennent pas de ce que signifie MMU : "Memory management unit" - lecture complémentaire sur Wikipédia

60voto

ZarathustrA Points 449
  1. fork() - crée un nouveau processus enfant, qui est une copie complète du processus parent. Les processus enfant et parent utilisent des espaces d'adressage virtuel différents, qui sont initialement peuplés des mêmes pages de mémoire. Puis, au fur et à mesure de l'exécution des deux processus, les espaces d'adressage virtuels commencent à différer de plus en plus, car le système d'exploitation effectue une copie paresseuse des pages de mémoire qui sont écrites par l'un ou l'autre de ces deux processus et attribue une copie indépendante des pages de mémoire modifiées pour chaque processus. Cette technique est appelée Copy-On-Write (COW).
  2. vfork() - crée un nouveau processus enfant, qui est une copie "rapide" du processus parent. Contrairement à l'appel système fork() Les processus enfant et parent partagent le même espace d'adressage virtuel. NOTE ! En utilisant le même espace d'adressage virtuel, le parent et l'enfant utilisent la même pile, le même pointeur de pile et le même pointeur d'instruction, comme dans le cas du classique fork() ! Pour éviter toute interférence indésirable entre le parent et l'enfant, qui utilisent la même pile, l'exécution du processus parent est gelée jusqu'à ce que l'enfant appelle soit exec() (création d'un nouvel espace d'adressage virtuel et transition vers une pile différente) ou _exit() (fin de l'exécution du processus). vfork() est l'optimisation de fork() pour le modèle "fork-and-exec". Il peut être exécuté 4 à 5 fois plus rapidement que le modèle de fork() car, contrairement à la fork() (même en gardant COW à l'esprit), mise en œuvre de vfork() ne comprend pas la création d'un nouvel espace d'adressage (l'allocation et la mise en place de nouveaux répertoires de pages).
  3. clone() - crée un nouveau processus enfant. Divers paramètres de cet appel système, spécifient quelles parties du processus parent doivent être copiées dans le processus enfant et quelles parties seront partagées entre eux. Par conséquent, cet appel système peut être utilisé pour créer toutes sortes d'entités d'exécution, en commençant par des threads et en terminant par des processus complètement indépendants. En fait, clone() est la base qui est utilisée pour la mise en œuvre de l'appel système pthread_create() et toute la famille de la fork() les appels système.
  4. exec() - réinitialise toute la mémoire du processus, charge et analyse le binaire exécutable spécifié, établit une nouvelle pile et passe le contrôle au point d'entrée de l'exécutable chargé. Cet appel système ne renvoie jamais le contrôle à l'appelant et sert à charger un nouveau programme dans un processus déjà existant. Cet appel système avec fork() l'appel système forment ensemble un modèle classique de gestion des processus UNIX appelé "fork-and-exec".

2 votes

Notez que les exigences BSD et POSIX en matière de vfork sont si faibles qu'il serait légal de faire vfork un synonyme de fork (et POSIX.1-2008 supprime vfork de la spécification entièrement). Si vous testez votre code sur un système qui les synonymise (par exemple, la plupart des BSDs post-4.4 à l'exception de NetBSD, les noyaux Linux pré-2.2.0-pre6, etc. vfork contracter, puis exploser si vous l'exécutez ailleurs. Certains de ceux qui le simulent avec fork (par exemple OpenBSD) garantissent toujours que le parent ne reprenne pas l'exécution jusqu'à ce que l'enfant exec ou _exit s. C'est ridiculement non-portable.

2 votes

Concernant la dernière phrase de votre 3ème point : j'ai remarqué sous Linux en utilisant strace que si le wrapper glibc pour fork() appelle le syscall clone, le wrapper pour vfork() appelle le syscall vfork.

11voto

user991800 Points 22

Les fonctions fork(), vfork() et clone() font toutes appel à do_fork() pour effectuer le vrai travail, mais avec des paramètres différents.

asmlinkage int sys_fork(struct pt_regs regs)
{
    return do_fork(SIGCHLD, regs.esp, &regs, 0);
}

asmlinkage int sys_clone(struct pt_regs regs)
{
    unsigned long clone_flags;
    unsigned long newsp;

    clone_flags = regs.ebx;
    newsp = regs.ecx;
    if (!newsp)
        newsp = regs.esp;
    return do_fork(clone_flags, newsp, &regs, 0);
}
asmlinkage int sys_vfork(struct pt_regs regs)
{
    return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.esp, &regs, 0);
}
#define CLONE_VFORK 0x00004000  /* set if the parent wants the child to wake it up on mm_release */
#define CLONE_VM    0x00000100  /* set if VM shared between processes */

SIGCHLD means the child should send this signal to its father when exit.

Pour fork, le père et l'enfant ont la table de pages VM indépendante, mais pour des raisons d'efficacité, fork ne va pas vraiment copier de pages, il va juste mettre toutes les pages inscriptibles en lecture seule pour le processus enfant. Ainsi, lorsque le processus enfant veut écrire quelque chose sur cette page, une exception se produit et le noyau alloue une nouvelle page clonée à partir de l'ancienne page avec la permission d'écrire. C'est ce qu'on appelle "copy on write".

Pour vfork, la mémoire virtuelle est exactement par l'enfant et le père--à cause de cela, le père et l'enfant ne peuvent pas être éveillés simultanément car ils s'influenceront mutuellement. Donc le père dormira à la fin de "do_fork()" et se réveillera lorsque l'enfant appellera exit() ou execve() puisqu'il possédera alors une nouvelle table de pages. Voici le code (dans do_fork()) qui fait dormir le père.

if ((clone_flags & CLONE_VFORK) && (retval > 0))
down(&sem);
return retval;

Voici le code (dans mm_release() appelé par exit() et execve()) qui réveille le père.

up(tsk->p_opptr->vfork_sem);

Pour sys_clone(), c'est plus flexible puisque vous pouvez y entrer n'importe quels clone_flags. Ainsi, pthread_create() appelle cet appel système avec plusieurs clone_flags :

int clone_flags = (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGNAL | CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID | CLONE_SYSVSEM) ;

Résumé : les fonctions fork(), vfork() et clone() vont créer des processus enfants avec différents montages de partage de ressources avec le processus père. Nous pouvons également dire que les vfork() et clone() peuvent créer des threads (en fait ce sont des processus puisqu'ils ont une task_struct indépendante) puisqu'ils partagent la table des pages VM avec le processus père.

-5voto

Raj Kannan B. Points 17

Dans fork(), le processus enfant ou parent s'exécutera en fonction de la sélection du cpu Mais dans vfork(), l'enfant sera sûrement exécuté en premier. Après que l'enfant se soit terminé, le parent sera exécuté.

3 votes

Faux. vfork() peut simplement être mis en œuvre comme fork() .

0 votes

Après AnyFork(), il n'est pas défini qui exécute le premier parent / enfant.

5 votes

@Raj : Vous avez quelques malentendus conceptuels si vous pensez qu'après une bifurcation il y a une notion implicite d'ordre sériel. La bifurcation crée un nouveau et renvoie ensuite le contrôle aux deux processus (chacun renvoyant un différent pid ) - le système d'exploitation peut programmer le nouveau processus pour qu'il s'exécute en parallèle si cela a un sens (par exemple, plusieurs processeurs). Si, pour une raison quelconque, vous avez besoin que ces processus s'exécutent dans un ordre sériel particulier, vous avez alors besoin d'une synchronisation supplémentaire que la bifurcation ne fournit pas ; franchement, vous ne voudriez probablement même pas d'une bifurcation en premier lieu.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X