Quelles sont les différences entre fork
et exec
?
fork
comme le clonage: O
Quelles sont les différences entre fork
et exec
?
fork
comme le clonage: O
Réponses:
L'utilisation fork
et exec
illustre l'esprit d'UNIX en ce qu'il fournit un moyen très simple de démarrer de nouveaux processus.
L' fork
appel 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' fork
appel 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, fork
etexec
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 find
programme 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 fork
soi 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 fork
une 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 wait
pour l'enfant. Ils peuvent simplement charger l'enfant directement dans leur espace de processus.
Certaines implémentations UNIX ont une optimisation fork
qui utilise ce qu'elles appellent la copie sur écriture. C'est une astuce pour retarder la copie de l'espace de processus fork
jusqu'à ce que le programme tente de changer quelque chose dans cet espace. Ceci est utile pour les programmes utilisant uniquement fork
et 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' exec
appels (execl
, execle
,execve
etc.) , mais exec
dans le contexte signifie ici l' un d'eux.
Le diagramme suivant illustre l' fork/exec
opération typique dans laquelle le bash
shell est utilisé pour répertorier un répertoire avec la ls
commande:
+--------+
| 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
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.
exec
est un peu plus facile à saisir, 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 le dit @Steve Hawkins, exec
peut être utilisé après vous fork
pour exécuter dans le processus actuel l'exécutable cible.
pid < 0
et l' fork()
appel a échoué
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
exec
Appels 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 coursexec()
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 )
Fork crée une copie d'un processus d'appel. suit généralement la structure
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
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})
Ils sont utilisés ensemble pour créer un nouveau processus enfant. Tout d'abord, l'appel fork
crée une copie du processus en cours (le processus enfant). Ensuite, exec
est 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
}
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.
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.‖
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:
fork()
renvoie l'identificateur de processus (pid) du processus enfant dans le parent
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;
}
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.)