web-dev-qa-db-fra.com

Différences entre fork et exec

Quelles sont les différences entre fork et exec?

182
Sashi

L'utilisation de fork et exec illustre l'esprit d'UNIX en ce sens qu'elle fournit un moyen très simple de démarrer de nouveaux processus.

L’appel fork crée essentiellement un duplicata du processus en cours, identique dans presque tous les sens (tout n’est pas copié, par exemple, les ressources sont limitées 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 différent (PID) et utilise le PID de l'ancien processus (parent) comme PID parent (PPID). Étant donné que les deux processus exécutent maintenant exactement le même code, ils peuvent déterminer lequel correspond au code de retour fork - l'enfant obtient 0, le parent obtient le PID de l'enfant. C’est tout, bien sûr, en supposant que l’appel fork fonctionne - sinon, aucun enfant n’est créé et le parent reçoit un code d’erreur.

L’appel exec permet de remplacer l’ensemble du processus en cours par un nouveau programme. Il charge le programme dans l'espace de processus actuel et l'exécute à partir du point d'entrée.

Ainsi, fork et exec sont souvent utilisés en séquence pour lancer un nouveau programme en tant qu'enfant d'un processus en cours. Les shells font généralement cela lorsque vous essayez d'exécuter un programme comme find - le Shell forks, puis l'enfant charge le programme find en mémoire, en configurant tous les arguments de la ligne de commande, les E/S standard, etc.

Mais ils ne sont pas tenus d'être utilisés ensemble. Il est parfaitement acceptable pour un programme de fork lui-même sans execing si, par exemple, le programme contient à la fois un code parent et un code enfant (vous devez faire attention à ce que vous faites, chaque implémentation peut avoir des restrictions). Cela a été beaucoup utilisé (et le reste) pour les démons qui écoutent simplement sur un port TCP et fork une copie d'eux-mêmes pour traiter une requête spécifique pendant que le parent retourne à l'écoute.

De même, les programmes qui savent qu'ils sont finis et veulent simplement exécuter un autre programme n'ont pas besoin de fork, exec et ensuite wait pour l'enfant. Ils peuvent simplement charger l'enfant directement dans leur espace de processus.

Certaines implémentations UNIX ont une fork optimisée qui utilise ce qu'elles appellent une copie sur écriture. C'est une astuce pour retarder la copie de l'espace de processus dans fork jusqu'à ce que le programme tente de modifier quelque chose dans cet espace. Ceci est utile pour les programmes utilisant uniquement fork et non exec, dans la mesure où ils ne doivent pas copier tout un espace de processus.

Si la exec est est appelée après fork (et c'est ce qui se produit le plus souvent), cela entraîne une écriture dans l'espace de processus, qui est ensuite copié pour le processus enfant.

Notez qu'il existe toute une famille d'appels exec (execl, execle, execve et ainsi de suite), mais exec dans le contexte signifie ici aucun des deux.

Le diagramme suivant illustre l'opération fork/exec typique dans laquelle le shell bash est utilisé pour répertorier un répertoire avec la commande ls:

+--------+
| pid=7  |
| ppid=4 |
| bash   |
+--------+
    |
    | calls fork
    V
+--------+             +--------+
| pid=7  |    forks    | pid=22 |
| ppid=4 | ----------> | ppid=7 |
| bash   |             | bash   |
+--------+             +--------+
    |                      |
    | waits for pid 22     | calls exec to run ls
    |                      V
    |                  +--------+
    |                  | pid=22 |
    |                  | ppid=7 |
    |                  | ls     |
    V                  +--------+
+--------+                 |
| pid=7  |                 | exits
| ppid=4 | <---------------+
| bash   |
+--------+
    |
    | continues
    V
334
paxdiablo

fork() divise le processus en cours en deux processus. Ou, en d’autres termes, votre programme facile à penser Nice linéaire devient soudain deux programmes séparés exécutant un code:

 int pid = fork();

 if (pid == 0)
 {
     printf("I'm the child");
 }
 else
 {
     printf("I'm the parent, my child is %i", pid);
     // here we can kill the child, but that's not very parently of us
 }

Cela peut en quelque sorte souffler votre esprit. Vous avez maintenant un morceau de code avec un état à peu près identique exécuté par deux processus. Le processus enfant hérite de tout le code et de la mémoire du processus qui l'a créé, y compris à partir de l'endroit où l'appel fork() vient de s'interrompre. La seule différence est le code de retour fork() qui vous indique si vous êtes le parent ou l’enfant. Si vous êtes le parent, la valeur de retour est l'id de l'enfant.

exec est un peu plus facile à comprendre, vous dites simplement à exec d'exécuter un processus en utilisant l'exécutable cible et vous n'avez pas deux processus exécutant le même code ou héritant du même état. Comme @Steve Hawkins le dit, exec peut être utilisé après forkpour exécuter dans le processus en cours l'exécutable cible.

49
Doug T.

Je pense que certains concepts de "Advanced Unix Programming" de Marc Rochkind ont été utiles pour comprendre les différents rôles de fork()exec(), en particulier pour les habitués du modèle Windows CreateProcess():

Un _/programme est une collection d'instructions et de données conservées dans un fichier normal sur disque. (de 1.1.2 Programmes, processus et threads)

.

Pour exécuter un programme, le noyau est d’abord invité à créer un nouveau process, qui est un environnement dans lequel un programme s’exécute. (également de 1.1.2 Programmes, processus et threads)

.

Il est impossible de comprendre les appels système exec ou fork sans une bonne compréhension de la distinction entre un processus et un programme. Si ces termes sont nouveaux pour vous, vous voudrez peut-être revenir en arrière et consulter la section 1.1.2. Si vous êtes prêt à procéder maintenant, nous résumerons la distinction en une phrase: Un processus est un environnement d'exécution qui comprend des segments d'instruction, de données utilisateur et de système, ainsi que de nombreuses autres ressources acquises au moment de l'exécution. , alors qu’un programme est un fichier contenant des instructions et des données utilisées pour initialiser les segments d’instruction et de données utilisateur d’un processus. (à partir de 5,3 exec appels système)

Une fois que vous avez compris la distinction entre un programme et un processus, le comportement des fonctions fork() et exec() peut être résumé comme suit:

  • fork() crée un duplicata du processus en cours
  • exec() remplace le programme dans le processus en cours par un autre programme

(Ceci est essentiellement une version simplifiée 'pour les nuls' de la réponse beaucoup plus détaillée de de paxdiablo )

29
Michael Burr

Fork crée une copie d'un processus d'appel. suit généralement la structure enter image description here

int cpid = fork( );

if (cpid = = 0) 
{

  //child code

  exit(0);

}

//parent code

wait(cpid);

// end

(pour le texte du processus enfant (code), les données, la pile est la même chose que le processus appelant) le processus enfant exécute le code si bloqué.

EXEC remplace le processus en cours par le nouveau code du processus, les données, la pile . Suit généralement la structure enter image description here

int cpid = fork( );

if (cpid = = 0) 
{

  //child code

  exec(foo);

  exit(0);

}

//parent code

wait(cpid);

// end

(après l'appel exec, le noyau unix efface le texte du processus enfant, les données, la pile et se remplit avec le texte/les données liés au processus foo) ainsi, le processus enfant utilise un code différent (le code de foo {différent de parent})

27
Sandesh Kobal

Ils sont utilisés ensemble pour créer un nouveau processus enfant. Tout d'abord, l'appel de fork crée une copie du processus en cours (le processus enfant). Ensuite, exec est appelé depuis le processus enfant pour "remplacer" la copie du processus parent par le nouveau processus.

Le processus ressemble à ceci:

child = fork();  //Fork returns a PID for the parent process, or 0 for the child, or -1 for Fail

if (child < 0) {
    std::cout << "Failed to fork GUI process...Exiting" << std::endl;
    exit (-1);
} else if (child == 0) {       // This is the Child Process
    // Call one of the "exec" functions to create the child process
    execvp (argv[0], const_cast<char**>(argv));
} else {                       // This is the Parent Process
    //Continue executing parent process
}
7
Steve Hawkins

fork () crée une copie du processus en cours, avec exécution dans le nouvel enfant à partir de juste après l'appel de fork (). Après le fork (), ils sont identiques, à l'exception de la valeur de retour de la fonction fork (). (RTFM pour plus de détails.) Les deux processus peuvent alors diverger davantage, l'un ne pouvant pas interférer avec l'autre, sauf éventuellement via les descripteurs de fichiers partagés.

exec () remplace le processus actuel par un nouveau. Cela n'a rien à voir avec fork (), sauf qu'un exec () suit souvent fork () quand on veut, c'est lancer un processus enfant différent, plutôt que remplacer le processus actuel.

4
Warren Young

La principale différence entre fork() et exec() est que,

L'appel système fork() crée un clone du programme en cours d'exécution. Le programme d'origine continue l'exécution avec la ligne de code suivante après l'appel de la fonction fork (). Le clone commence également l'exécution à la ligne de code suivante. Regardez le code suivant que j'ai obtenu de http://timmurphy.org/2014/04/26/using-fork-in-cc-a-minimum-working-example/

#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv)
{
    printf("--beginning of program\n");
    int counter = 0;
    pid_t pid = fork();
    if (pid == 0)
    {
        // child process
        int i = 0;
        for (; i < 5; ++i)
        {
            printf("child process: counter=%d\n", ++counter);
        }
    }
    else if (pid > 0)
    {
        // parent process
        int j = 0;
        for (; j < 5; ++j)
        {
            printf("parent process: counter=%d\n", ++counter);
        }
    }
    else
    {
        // fork failed
        printf("fork() failed!\n");
        return 1;
    }
    printf("--end of program--\n");
    return 0;
}

Ce programme déclare une variable de compteur, définie sur zéro, avant fork()ing. Après l'appel à la fourchette, deux processus s'exécutant en parallèle, chacun incrémentant sa propre version du compteur. Chaque processus s'achève et se termine. Comme les processus se déroulent en parallèle, nous n’avons aucun moyen de savoir lequel finira le premier. L'exécution de ce programme imprimera quelque chose de similaire à ce qui est présenté ci-dessous, bien que les résultats puissent varier d'une exécution à l'autre.

--beginning of program
parent process: counter=1
parent process: counter=2
parent process: counter=3
child process: counter=1
parent process: counter=4
child process: counter=2
parent process: counter=5
child process: counter=3
--end of program--
child process: counter=4
child process: counter=5
--end of program--

La famille d'appels système exec() remplace le code d'un processus en cours d'exécution par un autre élément de code. Le processus conserve son PID mais il devient un nouveau programme. Par exemple, considérons le code suivant: 

#include <stdio.h> 
#include <unistd.h> 
main() {
 char program[80],*args[3];
 int i; 
printf("Ready to exec()...\n"); 
strcpy(program,"date"); 
args[0]="date"; 
args[1]="-u"; 
args[2]=NULL; 
i=execvp(program,args); 
printf("i=%d ... did it work?\n",i); 
} 

Ce programme appelle la fonction execvp() pour remplacer son code par le programme de date. Si le code est stocké dans un fichier nommé exec1.c, son exécution produit la sortie suivante: 

Ready to exec()... 
Tue Jul 15 20:17:53 UTC 2008 

Le programme affiche la ligne ―Ready to exec (). . . ‖ Et après avoir appelé la fonction execvp (), remplace son code par le programme de date. Notez que la ligne -. . . Cela a-t-il fonctionné? ne s'affiche pas, car le code a alors été remplacé. Au lieu de cela, nous voyons le résultat de l'exécution de ―date -u.‖ 

2
Abdulhakim Zeinu