Qu'est-ce que la fonction exec()
et sa famille? Pourquoi cette fonction est-elle utilisée et comment fonctionne-t-elle?
S'il vous plaît, n'importe qui peut expliquer ces fonctions.
De manière simpliste, sous UNIX, vous avez le concept de processus et de programmes. Un processus est quelque chose dans lequel un programme s'exécute.
L'idée simple derrière le "modèle d'exécution" UNIX est qu'il existe deux opérations que vous pouvez effectuer.
La première consiste à fork()
, qui crée un tout nouveau processus contenant un duplicata du programme en cours, y compris son état. Il existe quelques différences entre les processus qui leur permettent de déterminer qui est le parent et lequel est l’enfant.
La seconde consiste à exec()
, qui remplace le programme dans le processus en cours par un tout nouveau programme.
A partir de ces deux opérations simples, l'ensemble du modèle d'exécution UNIX peut être construit.
Pour ajouter plus de détails à ce qui précède:
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 une copie presque identique du processus en cours, identique dans presque tous les sens (pas , tout est copié, par exemple , ressources limitées dans certaines implémentations, mais l’idée est de créer une copie aussi proche que possible). Un processus appelle fork()
alors que deux processus en reviennent - cela semble bizarre mais c'est vraiment très élégant
Le nouveau processus (appelé enfant) obtient un ID de processus différent (PID) et utilise le PID de l'ancien processus (le parent) comme PID parent (PPID).
Étant donné que les deux processus exécutent maintenant exactement le même code, ils doivent pouvoir savoir lequel est le code de retour de fork()
qui fournit cette information - l'enfant obtient 0, le parent obtient le PID du child (si la fork()
échoue, aucun enfant n'est créé et le parent obtient un code d'erreur). Ainsi, le parent connaît le PID de l’enfant et peut communiquer avec lui, le tuer, l’attendre, etc. (l’enfant peut toujours trouver son processus parent en appelant getppid()
).
L'appel exec()
remplace l'intégralité du contenu actuel du processus 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 quand 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 ligne de commande, les E/S standard et etc.
Mais ils ne sont pas tenus d'être utilisés ensemble. Il est parfaitement acceptable pour un programme d'appeler fork()
sans le suivre exec()
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 est toujours utilisé) pour les démons qui écoutent simplement sur un port TCP et bifurquent une copie d'eux-mêmes pour traiter une requête spécifique pendant que le parent retourne à l'écoute. Dans ce cas, le programme contient à la fois le parent et le code enfant .
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()/waitpid()
pour l'enfant. Ils peuvent simplement charger l'enfant directement dans leur espace de processus actuel avec exec()
.
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. Sous Linux, fork()
ne copie que les tables de pages et une nouvelle structure de tâches, exec()
effectuera le travail fastidieux de "séparation" de la mémoire des deux processus.
Si le exec
s'appelle après fork
(et c'est ce qui se passe le plus souvent), cela entraîne une écriture dans le processus et il est ensuite copié pour le processus enfant.
Linux a aussi une vfork()
, encore plus optimisée, qui partage à peu près tout entre les deux processus. À cause de cela, il y a certaines restrictions dans ce que l'enfant peut faire et le parent s'arrête jusqu'à ce que l'enfant appelle exec()
ou _exit()
.
Le parent doit être arrêté (et l'enfant n'est pas autorisé à revenir de la fonction actuelle) car les deux processus partagent même la même pile. Ceci est légèrement plus efficace pour le cas d'utilisation classique de fork()
suivi immédiatement de exec()
.
Notez qu'il existe toute une famille d'appels exec
(execl
, execle
, execve
et ainsi de suite) mais exec
dans leur contexte ici signifie l'un d'eux.
Le diagramme suivant illustre le fork/exec
opération 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
Les fonctions de la famille exec () ont des comportements différents:
Vous pouvez les mélanger, donc vous avez:
Pour tous, l'argument initial est le nom d'un fichier à exécuter.
Pour plus d'informations, consultez page de manuel exec (3) :
man 3 exec # if you are running a UNIX system
La famille de fonctions exec
oblige votre processus à exécuter un programme différent, en remplacement de l'ancien programme qu'il exécutait. I.e., si vous appelez
execl("/bin/ls", "ls", NULL);
puis le programme ls
est exécuté avec l'id du processus, le répertoire de travail en cours et l'utilisateur/groupe (droits d'accès) du processus appelé execl
. Ensuite, le programme original ne fonctionne plus.
Pour démarrer un nouveau processus, l'appel système fork
est utilisé. Pour exécuter un programme sans remplacer l'original, vous devez fork
, puis exec
.
exec
est souvent utilisé en conjonction avec fork
, ce à quoi j'ai vu que vous avez également posé des questions, alors je vais en discuter en gardant cela à l'esprit.
exec
transforme le processus en cours en un autre programme. Si vous avez déjà regardé Doctor Who, c'est comme quand il se régénère: son ancien corps est remplacé par un nouveau.
La façon dont cela se produit avec votre programme et exec
est qu'une grande partie des ressources vérifiées par le noyau du système d'exploitation vérifie si le fichier que vous transmettez à exec
en tant qu'argument du programme (premier argument). est exécutable par l'utilisateur actuel (id utilisateur du processus appelant exec
) et, dans l'affirmative, remplace le mappage de la mémoire virtuelle du processus actuel par une mémoire virtuelle du nouveau processus et copie le argv
et envp
données transmises lors de l'appel exec
dans une zone de cette nouvelle mappe de mémoire virtuelle. Plusieurs autres choses peuvent également se produire ici, mais les fichiers qui étaient ouverts pour le programme appelé exec
seront toujours ouverts pour le nouveau programme et ils partageront le même ID de processus, mais le programme qui s'appelle exec
cessera (sauf si exec a échoué).
La raison pour laquelle cela est fait de cette manière est que, en séparant en cours d'exécutionanouveaprogramme en deux étapes comme celle-ci, vous peut faire certaines choses entre les deux étapes. La chose la plus courante à faire est de s’assurer que certains fichiers sont ouverts dans le nouveau programme en tant que descripteurs de fichiers. (rappelez-vous ici que les descripteurs de fichier ne sont pas les mêmes que FILE *
, mais sont int
valeurs connues du noyau). En faisant cela, vous pouvez:
int X = open("./output_file.txt", O_WRONLY);
pid_t fk = fork();
if (!fk) { /* in child */
dup2(X, 1); /* fd 1 is standard output,
so this makes standard out refer to the same file as X */
close(X);
/* I'm using execl here rather than exec because
it's easier to type the arguments. */
execl("/bin/echo", "/bin/echo", "hello world");
_exit(127); /* should not get here */
} else if (fk == -1) {
/* An error happened and you should do something about it. */
perror("fork"); /* print an error message */
}
close(X); /* The parent doesn't need this anymore */
Ceci accomplit l'exécution:
/bin/echo "hello world" > ./output_file.txt
à partir de la commande Shell.
quelle est la fonction exec et sa famille.
La famille de fonctions exec
regroupe toutes les fonctions utilisées pour exécuter un fichier, telles que execl
, execlp
, execle
, execv
et execvp
. Ils sont tous des interfaces pour execve
et fournissent différentes méthodes pour l'appeler.
pourquoi cette fonction est utilisée
Les fonctions Exec sont utilisées lorsque vous souhaitez exécuter (lancer) un fichier (programme).
et comment ça marche.
Ils travaillent en remplaçant l'image de processus actuelle par celle que vous avez lancée. Ils remplacent (en terminant) le processus en cours d'exécution (celui qui a appelé la commande exec) par le nouveau processus lancé.
Pour plus de détails: voir ce lien .
La exec(3,3p)
fonctions remplace le processus en cours par un autre. En d’autres termes, le processus en cours s’arrête, et un autre s’exécute à la place, reprenant certaines des ressources du programme original.
Lorsqu'un processus utilise fork (), il crée une copie de lui-même et devient le fils du processus. Fork () est implémenté à l’aide de l’appel système clone () sous linux, qui retourne deux fois à partir du noyau.
Voyons cela avec un exemple:
pid = fork();
// Both child and parent will now start execution from here.
if(pid < 0) {
//child was not created successfully
return 1;
}
else if(pid == 0) {
// This is the child process
// Child process code goes here
}
else {
// Parent process code goes here
}
printf("This is code common to parent and child");
Dans l'exemple, nous avons supposé que exec () n'était pas utilisé dans le processus enfant.
Mais un parent et un enfant diffèrent par certains attributs du PCB (process control block). Ceux-ci sont:
Mais qu'en est-il de la mémoire de l'enfant? Un nouvel espace d'adressage est-il créé pour un enfant?
Les réponses dans non. Après le fork (), le parent et l’enfant partagent l’espace adresse mémoire du parent. Sous Linux, ces espaces d'adressage sont divisés en plusieurs pages. Ce n'est que lorsque l'enfant écrit dans l'une des pages de mémoire parent qu'un duplicata de cette page est créé pour l'enfant. Cette opération est également appelée copie à l'écriture (copie des pages parentes uniquement lorsque l'enfant y écrit).
Comprenons la copie en écriture avec un exemple.
int x = 2;
pid = fork();
if(pid == 0) {
x = 10;
// child is changing the value of x or writing to a page
// One of the parent stack page will contain this local variable. That page will be duplicated for child and it will store the value 10 in x in duplicated page.
}
else {
x = 4;
}
Mais pourquoi une copie sur écriture est-elle nécessaire?
Une création de processus typique a lieu via une combinaison fork () - exec (). Voyons d’abord ce que fait exec ().
Le groupe de fonctions Exec () remplace l’espace adresse de l’enfant par un nouveau programme. Une fois que exec () est appelé dans un enfant, un espace adresse distinct sera créé pour l’enfant, lequel est totalement différent de celui du parent.
Si aucun mécanisme de copie en écriture n’était associé à fork (), des pages dupliquées auraient été créées pour l’enfant et toutes les données auraient été copiées dans ses pages. L’allocation de nouvelles mémoires et la copie de données sont des processus très coûteux (cela prend du temps au processeur et d’autres ressources système). Nous savons également que dans la plupart des cas, l’enfant appellera exec () et que cela remplacerait la mémoire de l’enfant par un nouveau programme. Donc, la première copie que nous avons faite aurait été un gaspillage si la copie sur écriture n'était pas là.
pid = fork();
if(pid == 0) {
execlp("/bin/ls","ls",NULL);
printf("will this line be printed"); // Think about it
// A new memory space will be created for the child and that memory will contain the "/bin/ls" program(text section), it's stack, data section and heap section
else {
wait(NULL);
// parent is waiting for the child. Once child terminates, parent will get its exit status and can then continue
}
return 1; // Both child and parent will exit with status code 1.
Pourquoi le parent attend-il un processus enfant?
Pourquoi l'appel système exec () est-il nécessaire?
Il n’est pas nécessaire d’exécuter exec () avec fork (). Si le code que l'enfant exécutera se trouve dans le programme associé à parent, exec () n'est pas nécessaire.
Mais pensons aux cas où l’enfant doit exécuter plusieurs programmes. Prenons l’exemple du programme Shell. Il prend en charge plusieurs commandes telles que find, mv, cp, date, etc. Aura-t-il raison d'inclure le code de programme associé à ces commandes dans un programme ou si l'enfant charge ces programmes dans la mémoire si nécessaire?
Tout dépend de votre cas d'utilisation. Vous avez un serveur Web qui donne une entrée x qui renvoie le 2 ^ x aux clients. Pour chaque demande, le serveur Web crée un nouvel enfant et lui demande de le calculer. Allez-vous écrire un programme séparé pour calculer cela et utiliser exec ()? Ou vous allez simplement écrire du code de calcul dans le programme parent?
Généralement, la création d'un processus implique une combinaison d'appels fork (), exec (), wait () et exit ().