Différences entre fork et exec

199

Quelles sont les différences entre forket exec?

Sashi
la source
3
Un bon résumé détaillé des fonctions fork, exec et autres fonctions de contrôle de processus se trouve sur yolinux.com/TUTORIALS/ForkExecProcesses.html
Jonathan Fingland
9
@Justin, parce que nous voulons que SO devienne l' endroit où aller pour les questions de programmation.
paxdiablo
4
@ Polaris878: oh, c'est le cas maintenant! : D
Janusz Lenar
tout forkcomme le clonage: O
Sebastian Hojas

Réponses:

364

L'utilisation forket execillustre l'esprit d'UNIX en ce qu'il fournit un moyen très simple de démarrer de nouveaux processus.

L' forkappel fait essentiellement un doublon du processus actuel, identique dans presque tous les sens. 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 différent (PID) et a le PID de l'ancien processus (parent) comme PID parent (PPID). Étant donné que les deux processus exécutent désormais exactement le même code, ils peuvent dire lequel est le code de retour defork - l'enfant obtient 0, le parent obtient le PID de l'enfant. C'est tout, bien sûr, en supposant que l' forkappel fonctionne - sinon, aucun enfant n'est créé et le parent obtient un code d'erreur.

le exec appel est un moyen de remplacer fondamentalement l'ensemble du processus actuel par un nouveau programme. Il charge le programme dans l'espace de processus actuel et l'exécute à partir du point d'entrée.

Alors, forketexec sont souvent utilisés en séquence pour faire fonctionner un nouveau programme en tant qu'enfant d'un processus en cours. Les shells font généralement cela chaque fois que vous essayez d'exécuter un programme comme find- le shell, puis l'enfant charge le findprogramme en mémoire, en configurant tous les arguments de ligne de commande, les E / S standard, etc.

Mais ils ne sont pas tenus d'être utilisés ensemble. C'est parfaitement acceptable pour un programme en forksoi sansexec ing si, par exemple, le programme contient à la fois le code parent et le code enfant (vous devez faire attention à ce que vous faites, chaque implémentation peut avoir des restrictions). Cela a été beaucoup utilisé (et l'est toujours) pour les démons qui écoutent simplement sur un port TCP et forkune copie d'eux-mêmes pour traiter une demande spécifique pendant que le parent recommence à écouter.

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

Certaines implémentations UNIX ont une optimisation forkqui utilise ce qu'elles appellent la copie sur écriture. C'est une astuce pour retarder la copie de l'espace de processus forkjusqu'à ce que le programme tente de changer quelque chose dans cet espace. Ceci est utile pour les programmes utilisant uniquement forket nonexec dans la mesure où ils n'ont pas à copier un espace de processus complet.

Si le exec est appelé suivant fork(et c'est ce qui arrive le plus souvent), cela provoque une écriture dans l'espace de processus et il est ensuite copié pour le processus enfant.

Notez qu'il existe toute une famille d' execappels (execl , execle,execve etc.) , mais execdans le contexte signifie ici l' un d'eux.

Le diagramme suivant illustre l' fork/execopération typique dans laquelle le bashshell est utilisé pour répertorier un répertoire avec la lscommande:

+--------+
| 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
paxdiablo
la source
52

fork()divise le processus en cours en deux processus. Ou en d'autres termes, votre joli programme linéaire facile à penser devient soudainement deux programmes distincts exécutant un morceau de 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 vous couper le souffle. 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 vient de le créer, y compris à partir de l'endroit où l' fork()appel vient de s'arrêter. La seule différence est lafork() code retour pour vous dire si vous êtes le parent ou l'enfant. Si vous êtes le parent, la valeur de retour est l'ID de l'enfant.

execest un peu plus facile à saisir, vous dites simplement execd'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 le dit @Steve Hawkins, execpeut être utilisé après vous forkpour exécuter dans le processus actuel l'exécutable cible.

Doug T.
la source
6
il y a aussi la condition quand pid < 0et l' fork()appel a échoué
Jonathan Fingland
3
Cela ne me souffle pas du tout :-) Un morceau de code exécuté par deux processus se produit chaque fois qu'une bibliothèque partagée ou une DLL est utilisée.
paxdiablo
31

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 quelqu'un habitué au CreateProcess()modèle Windows :

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

.

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

.

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

Une fois que vous comprenez la distinction entre un programme et un processus, le comportement fork()et la exec()fonction peuvent être résumés comme suit:

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

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

Michael Burr
la source
29

Fork crée une copie d'un processus d'appel. suit généralement la structure entrez la description de l'image ici

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 identique au processus appelant) le processus enfant exécute le code dans le bloc if.

EXEC remplace le processus actuel par le nouveau code, les données et la pile du processus. suit généralement la structure entrez la description de l'image ici

int cpid = fork( );

if (cpid = = 0) 
{   
  //child code

  exec(foo);

  exit(0);    
}

//parent code

wait(cpid);

// end

(après un appel à un noyau, le noyau Unix efface le texte, les données, la pile du processus enfant et le remplit avec du texte / des données liés au processus foo) ainsi le processus enfant est avec un code différent (le code de foo {n'est pas le même que le parent})

Sandesh Kobal
la source
1
C'est un peu sans rapport avec la question, mais ce code ci-dessus ne provoque-t-il pas une condition de concurrence si le processus enfant arrive à terminer son code en premier? Dans ce cas, le processus parental resterait à jamais attendre que l'enfant se termine, non?
stdout
7

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

Le processus se déroule comme 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
}
Steve Hawkins
la source
2
En 7ème ligne, il est mentionné que la fonction exec () crée le processus enfant. Est-ce vraiment le cas parce que fork () a déjà créé le processus enfant et l'appel exec () remplace simplement le programme du nouveau processus qui vient d'être créé
cbinder
4

fork () crée une copie du processus en cours, avec exécution dans le nouvel enfant à partir de juste après l'appel fork (). Après la fourche (), 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 encore plus, l'un ne pouvant pas interférer avec l'autre, sauf éventuellement via des 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 () lorsque ce qui est voulu est de lancer un processus enfant différent, plutôt que de remplacer le processus actuel.

Warren Young
la source
3

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

L' fork()appel système crée un clone du programme en cours d'exécution. Le programme d'origine continue son exécution avec la ligne de code suivante après l'appel de la fonction fork (). Le clone démarre é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, mise à zéro, avant fork()ing. Après l'appel de fork, nous avons deux processus exécutés en parallèle, tous deux incrémentant leur propre version de compteur. Chaque processus se terminera et se terminera. Parce que les processus s'exécutent en parallèle, nous n'avons aucun moyen de savoir lequel se terminera en premier. L'exécution de ce programme imprimera quelque chose de similaire à ce qui est illustré 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 exec()famille d'appels système remplace le code en cours d'exécution d'un processus par un autre morceau de code. Le processus conserve son PID mais il devient un nouveau programme. Par exemple, considérez 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 execvp()fonction 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 sort la ligne ―Prêt à exec (). . . ‖ Et après avoir appelé la fonction execvp (), remplace son code par le programme date. Notez que la ligne -. . . did it work‖ ne s'affiche pas, car à ce stade, le code a été remplacé. Au lieu de cela, nous voyons la sortie de l'exécution de ―date -u.‖

Abdulhakim Zeinu
la source
1

entrez la description de l'image icifork():

Il crée une copie du processus en cours. Le processus en cours est appelé processus parent et le processus nouvellement créé est appelé processus enfant . La façon de différencier les deux est en regardant la valeur retournée:

  1. fork() renvoie l'identificateur de processus (pid) du processus enfant dans le parent

  2. fork() renvoie 0 chez l'enfant.

exec():

Il initie un nouveau processus au sein d'un processus. Il charge un nouveau programme dans le processus actuel, remplaçant celui existant.

fork() + exec() :

Lors du lancement d'un nouveau programme, il faut tout d'abord fork()créer un nouveau processus, puis exec()(c'est-à-dire charger en mémoire et exécuter) le programme binaire qu'il est censé exécuter.

int main( void ) 
{
    int pid = fork();
    if ( pid == 0 ) 
    {
        execvp( "find", argv );
    }

    //Put the parent to sleep for 2 sec,let the child finished executing 
    wait( 2 );

    return 0;
}
Yogeesh HT
la source
0

Le meilleur exemple pour comprendre le concept fork()et exec()est le shell , le programme interpréteur de commandes que les utilisateurs exécutent généralement après s'être connecté au système. Le shell interprète le premier mot de la ligne de commande comme une commande nom de

Pour de nombreuses commandes, les fourches shell et les exécutables du processus enfant la commande associée au nom traitant les mots restants sur la ligne de commande comme paramètres de la commande.

Le shell permet trois types de commandes. Tout d'abord, une commande peut être un fichier exécutable qui contient du code objet produit par compilation du code source (un programme C par exemple). Deuxièmement, une commande peut être un fichier exécutable qui contient une séquence de lignes de commande shell. Enfin, une commande peut être une commande shell interne (au lieu d'un fichier exécutable ex-> cd , ls etc.)

krpra
la source