Quelle est la exec()
fonction et sa famille? Pourquoi cette fonction est-elle utilisée et comment fonctionne-t-elle?
S'il vous plaît, n'importe qui explique ces fonctions.
Quelle est la exec()
fonction et sa famille? Pourquoi cette fonction est-elle utilisée et comment fonctionne-t-elle?
S'il vous plaît, n'importe qui explique ces fonctions.
Réponses:
Simplement, sous UNIX, vous avez le concept de processus et de programmes. Un processus est un environnement dans lequel un programme s'exécute.
L'idée simple derrière le «modèle d'exécution» UNIX est qu'il y a deux opérations que vous pouvez effectuer.
Le premier est to fork()
, qui crée un tout nouveau processus contenant un duplicata (principalement) du programme actuel, y compris son état. Il existe quelques différences entre les deux processus qui leur permettent de déterminer qui est le parent et qui est l'enfant.
Le second est to exec()
, qui remplace le programme dans le processus actuel par un tout nouveau programme.
À partir de ces deux opérations simples, le modèle d'exécution UNIX entier 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'il offre un moyen très simple de démarrer de nouveaux processus.
L' fork()
appel fait un quasi-double 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). Un seul processus appelle fork()
mais deux processus reviennent de cet appel - cela semble bizarre mais c'est vraiment assez élégant
Le nouveau processus (appelé enfant) obtient un ID de processus différent (PID) et a le PID de l'ancien processus (le parent) comme son PID parent (PPID).
Parce que les deux processus sont en cours d' exécution maintenant exactement le même code, ils doivent être en mesure de dire qui est qui - le code de retour fork()
fournit ces informations - l'enfant obtient 0, le parent obtient le PID de l'enfant (si l' fork()
échec, pas l'enfant est créé et le parent reçoit un code d'erreur).
De cette façon, le parent connaît le PID de l'enfant et peut communiquer avec lui, le tuer, l'attendre et ainsi de suite (l'enfant peut toujours trouver son processus parent avec un appel à getppid()
).
L' exec()
appel remplace tout le 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.
Donc, fork()
et exec()
sont souvent utilisés en séquence pour faire exécuter 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 forks, puis l'enfant charge le find
programme en mémoire, définissant tous les arguments de ligne de commande, les E / S standard, etc.
Mais ils ne doivent pas être utilisés ensemble. Il est parfaitement acceptable pour un programme d'appeler fork()
sans suite exec()
si, par exemple, le programme contient à la fois du code parent et du 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 se chargent d'une copie d'eux-mêmes pour traiter une requête spécifique pendant que le parent revient à l'écoute. Dans ce cas, le programme contient à la fois le code parent et le code enfant.
De même, les programmes qui savent qu'ils sont terminés et qui veulent simplement exécuter un autre programme n'ont pas besoin de le faire fork()
, exec()
puis 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 version optimisée 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 non exec()
dans la mesure où ils n'ont pas à copier un espace de processus entier. Sous Linux, fork()
ne fait qu'une copie des tableaux de pages et une nouvelle structure de tâches, exec()
fera le gros travail de "séparation" de la mémoire des deux processus.
Si le exec
est appelé suivant fork
(et c'est ce qui se passe le plus souvent), cela provoque une écriture dans l'espace de processus et il est ensuite copié pour le processus enfant, avant que les modifications ne soient autorisées.
Linux a également un vfork()
, encore plus optimisé, qui partage à peu près tout entre les deux processus. Pour cette raison, il existe 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 courante) puisque 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 ya une famille de 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 où le bash
shell est utilisé pour lister 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
exec
l'utilitaire est-il utilisé pour rediriger les E / S du processus actuel? Comment le cas "nul", exécutant exec sans argument, a-t-il été utilisé pour cette convention?
exec
au moyen de remplacer le programme actuel (le shell) dans ce processus par un autre, alors ne pas spécifier cet autre programme par lequel le remplacer peut simplement signifier que vous ne voulez pas le remplacer.
exec
être appelé sans programme. Mais c'est un peu étrange dans ce scénario car l'utilité initiale de la redirection pour un nouveau programme - un programme qui serait effectivement exec
utilisé - disparaît et vous avez un artefact utile, redirigeant le programme actuel - qui n'est pas exec
utilisé ou démarré en aucune façon - à la place.
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, lisez la page de manuel exec (3) :
man 3 exec # if you are running a UNIX system
execve()
votre liste, qui est définie par POSIX, et vous avez ajouté execvpe()
, qui n'est pas définie par POSIX (principalement pour des raisons de précédent historique; elle complète l'ensemble des fonctions). Sinon, une explication utile de la convention de dénomination pour la famille - un complément utile à paxdiablo 'une réponse qui explique plus sur le fonctionnement des fonctions.
execvpe()
(et al) ne répertorie pas execve()
; il a sa propre page de manuel séparée (au moins sur Ubuntu 16.04 LTS) - la différence étant que les autres exec()
fonctions de la famille sont répertoriées dans la section 3 (fonctions) alors qu'elles execve()
sont répertoriées dans la section 2 (appels système). Fondamentalement, toutes les autres fonctions de la famille sont implémentées en termes d'appel à execve()
.
La exec
famille de fonctions permet à votre processus d'exécuter un programme différent, remplaçant l'ancien programme qu'il exécutait. Ie, si vous appelez
execl("/bin/ls", "ls", NULL);
puis le ls
programme est exécuté avec l'identifiant du processus, le répertoire de travail actuel et l'utilisateur / groupe (droits d'accès) du processus qui a appelé execl
. Ensuite, le programme d'origine ne fonctionne plus.
Pour démarrer un nouveau processus, l' fork
appel système est utilisé. Pour exécuter un programme sans remplacer l'original, vous devez fork
alors exec
.
quelle est la fonction exec et sa famille.
La exec
famille de fonctions est toutes les fonctions utilisées pour exécuter un fichier, comme execl
, execlp
, execle
, execv
et execvp
.Ils sont tous frontend pour execve
et fournir des méthodes différentes de l' appeler.
pourquoi cette fonction est-elle utilisée
Les fonctions Exec sont utilisées lorsque vous souhaitez exécuter (lancer) un fichier (programme).
et comment ça marche.
Ils fonctionnent en écrasant la mémoire image actuelle par celle que vous avez lancée. Ils remplacent (en mettant fin) le processus en cours d'exécution (celui qui a appelé la commande exec) par le nouveau processus qui a été lancé.
Pour plus de détails: voir ce lien .
exec
est souvent utilisé en conjonction avec fork
, dont j'ai vu que vous avez également posé des questions, je vais donc en discuter avec cela à l'esprit.
exec
transforme le processus en cours en un autre programme. Si vous avez déjà regardé Doctor Who, alors c'est comme quand il se régénère - son ancien corps est remplacé par un nouveau corps.
La façon dont cela se produit avec votre programme et exec
est que beaucoup de ressources que le noyau du système d'exploitation vérifie pour voir si le fichier que vous passez en exec
tant qu'argument du programme (premier argument) est exécutable par l'utilisateur actuel (identifiant utilisateur du processus faire l' exec
appel) et si tel est le cas, il remplace le mappage de mémoire virtuelle du processus en cours par une mémoire virtuelle, le nouveau processus et copie les données argv
et envp
qui ont été transmises lors de l' exec
appel dans une zone de cette nouvelle carte de mémoire virtuelle. Plusieurs autres choses peuvent également se produire ici, mais les fichiers qui étaient ouverts pour le programme qui a appelé le exec
seront toujours pour le nouveau programme et ils partageront le même ID de processus, mais le programme qui a appelé exec
cessera (sauf si l'exécution échoue).
La raison pour laquelle cela est fait de cette manière est qu'en séparant l' exécution d' un nouveau programme en deux étapes comme celle-ci, vous pouvez faire certaines choses entre les deux étapes. La chose la plus courante à faire est de s'assurer que le nouveau programme a certains fichiers ouverts en tant que certains descripteurs de fichiers. (rappelez-vous ici que les descripteurs de fichiers ne sont pas les mêmes que FILE *
, mais sont des int
valeurs que le noyau connaît). 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
depuis le shell de commande.
Lorsqu'un processus utilise fork (), il crée une copie de lui-même et ces doublons deviennent l'enfant du processus. Le fork () est implémenté en utilisant l'appel système clone () sous linux qui retourne deux fois du noyau.
Comprenons 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'est pas utilisé dans le processus enfant.
Mais un parent et un enfant diffèrent dans certains des attributs PCB (bloc de contrôle de processus). 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 en non. Après le fork (), le parent et l'enfant partagent l'espace d'adressage mémoire du parent. Sous Linux, ces espaces d'adressage sont divisés en plusieurs pages. Uniquement lorsque l'enfant écrit sur l'une des pages de mémoire parent, une copie de cette page est créée pour l'enfant. Ceci est également connu sous le nom de copie à l'écriture (copie des pages parentes uniquement lorsque l'enfant y écrit).
Comprenons copie sur é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 la copie sur écriture est-elle nécessaire?
Une création de processus typique a lieu via la combinaison fork () - exec (). Comprenons d'abord ce que fait exec ().
Le groupe de fonctions Exec () remplace l'espace d'adressage de l'enfant par un nouveau programme. Une fois que exec () est appelé dans un enfant, un espace d'adressage séparé sera créé pour l'enfant qui est totalement différent de celui du parent.
S'il n'y avait pas de mécanisme de copie sur l'écriture associé à fork (), des pages dupliquées auraient été créées pour l'enfant et toutes les données auraient été copiées sur les pages de l'enfant. L'allocation d'une nouvelle mémoire et la copie de données est un processus très coûteux (prend du temps au processeur et d'autres ressources système). Nous savons également que dans la plupart des cas, l'enfant va appeler 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'utiliser exec () avec fork (). Si le code que l'enfant exécutera se trouve dans le programme associé au parent, exec () n'est pas nécessaire.
Mais pensez 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. Serait-il juste d'inclure le code de programme associé à ces commandes dans un programme ou de laisser l'enfant charger ces programmes dans la mémoire si nécessaire?
Tout dépend de votre cas d'utilisation. Vous avez un serveur Web qui a donné une entrée x qui renvoie le 2 ^ x aux clients. Pour chaque requête, le serveur Web crée un nouvel enfant et lui demande de 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?
Habituellement, la création d'un processus implique une combinaison d'appels fork (), exec (), wait () et exit ().
Les exec(3,3p)
fonctions remplacent le processus actuel par un autre. Autrement dit, le processus en cours s'arrête et un autre s'exécute à la place, reprenant certaines des ressources du programme d'origine.