Pourquoi les obus appellent-ils fork ()?


32

Lorsqu'un processus est démarré à partir d'un shell, pourquoi ce dernier se lance-t-il avant d'exécuter le processus?

Par exemple, lorsque l'utilisateur entre grep blabla foo, pourquoi le shell ne peut-il pas simplement appeler exec()grep sans un shell enfant?

En outre, lorsqu'un shell se place dans un émulateur de terminal à interface graphique, démarre-t-il un autre émulateur de terminal? (comme le pts/13démarrage pts/14)

Réponses:


34

Lorsque vous appelez une execméthode de famille, elle ne crée pas de nouveau processus, mais execremplace le processus en mémoire, le jeu d'instructions actuel, etc., par le processus que vous souhaitez exécuter.

Par exemple, vous voulez exécuter grepavec exec. bashest un processus (qui a une mémoire séparée, un espace d'adressage). Maintenant , quand vous appelez exec(grep), exec remplacera la mémoire de processus en cours, l' espace d'adressage, jeu d'instructions etc avec des grep'sdonnées. Cela signifie que le bashprocessus n'existera plus. En conséquence, vous ne pouvez plus revenir au terminal une fois la grepcommande terminée . C'est pourquoi les méthodes de la famille exec ne reviennent jamais. Vous ne pouvez pas exécuter de code après exec; c'est inaccessible.


Presque ok --- j'ai substitué Terminal avec bash. ;-)
Rmano

2
En passant, vous pouvez dire à bash d’exécuter grep sans forger d’abord, en utilisant la commande exec grep blabla foo. Bien sûr, dans ce cas particulier, cela ne sera pas très utile (puisque la fenêtre de votre terminal se fermera dès que le grep sera terminé), mais cela peut être utile à l’occasion (par exemple, si vous démarrez un autre shell, peut-être via ssh / sudo / screen, et n’avez pas l’intention de revenir à l’original, ou si le processus de shell sur lequel vous l’utilisez est un sous-shell qui n’a jamais été conçu pour exécuter plus d’une commande).
Ilmari Karonen

7
Jeu d'instructions a une signification très spécifique. Et ce n'est pas le sens dans lequel vous l'utilisez.
Andrew Savinykh

@IlmariKaronen Cela serait utile dans les scripts d'encapsulation, où vous voulez préparer les arguments et l'environnement pour une commande. Et le cas que vous avez mentionné, où bash n’est jamais censé exécuter plus d’une commande, c’est en fait bash -c 'grep foo bar'et que vous appelez exec il ya une forme d’optimisation automatiquement pour vous
Sergiy Kolodyazhnyy

3

Selon le pts, vérifiez vous-même: dans un shell, exécutez

echo $$ 

pour connaître votre identifiant de processus (PID), j'ai par exemple

echo $$
29296

Puis lancez par exemple sleep 60puis, dans un autre terminal

(0)samsung-romano:~% ps -edao pid,ppid,tty,command | grep 29296 | grep -v grep
29296  2343 pts/11   zsh
29499 29296 pts/11   sleep 60

Donc non, en général, vous avez le même terminal associé au processus. (Notez que ceci est votre sleepparce qu'il a votre shell comme parent).


2

TL; DR : Parce que c'est la méthode optimale pour créer de nouveaux processus et garder le contrôle dans un shell interactif.

fork () est nécessaire pour les processus et les tubes

Pour répondre à la partie spécifique de cette question, si elle grep blabla foodevait être appelée exec()directement via parent, le parent saisirait pour exister et son PID avec toutes les ressources serait repris par grep blabla foo.

Cependant, parlons en général de exec()et fork(). La raison principale de ce comportement est qu’il fork()/exec()s’agit de la méthode standard pour créer un nouveau processus sous Unix / Linux, et que ceci n’est pas spécifique à Bash; cette méthode est en place depuis le début et influencée par cette même méthode à partir des systèmes d’exploitation déjà existants. Pour paraphraser quelque peu la réponse de goldilocks sur une question connexe, il fork()est plus facile de créer un nouveau processus car le noyau a moins de travail à faire en ce qui concerne l'allocation des ressources et beaucoup de propriétés (telles que les descripteurs de fichier, l'environnement, etc.) - tout peut être hérité du processus parent (dans ce cas de bash).

Deuxièmement, en ce qui concerne les shells interactifs, vous ne pouvez pas exécuter une commande externe sans forking. Pour lancer un exécutable qui vit sur le disque (par exemple, /bin/df -h), vous devez appeler l’une des exec()fonctions de la famille, telle que execve()remplacer le parent par le nouveau processus, reprendre son PID et ses descripteurs de fichier existants, etc. Pour le shell interactif, vous souhaitez que le contrôle revienne à l'utilisateur et laisse le shell interactif parent continuer. Ainsi, le meilleur moyen est de créer un sous-processus via fork()et de le laisser prendre en charge via execve(). Ainsi, le shell interactif PID 1156 engendrerait un enfant via fork()PID 1157, puis appelera execve("/bin/df",["df","-h"],&environment), ce qui l’ /bin/df -hexécutera avec le PID 1157. Il ne reste plus qu’à attendre que le processus se termine et que le contrôle lui soit renvoyé.

Dans le cas où vous devez créer un canal entre deux commandes ou plus, par exemple df | grep, vous avez besoin d'un moyen de créer deux descripteurs de fichier (lecture et écriture en fin de canal qui proviennent de pipe()syscall), puis laissez en quelque sorte deux nouveaux processus en hériter. Cela consiste à créer un nouveau processus, puis à copier l'extrémité d'écriture du canal via dup2()call sur son stdoutalias fd 1 (ainsi, si write end est à fd 4, nous le faisons dup2(4,1)). Lorsqu'il exec()se dfproduit, le processus enfant ne pense plus à rien stdoutet écrit dessus sans se rendre compte (à moins qu'il ne vérifie activement) que sa sortie est réellement dirigée. Le même processus se produit grep, sauf que nous fork()prenons la fin du tuyau avec fd 3 et dup(3,0)avant le frai grepavecexec(). Pendant tout ce temps, le processus parent est toujours là, attendant de reprendre le contrôle une fois le pipeline terminé.

Dans le cas de commandes intégrées, généralement pas le shell fork(), à l'exception de la sourcecommande. Sous-coquilles exigent fork().

En bref, il s’agit d’un mécanisme nécessaire et utile.

Inconvénients de forking et optimisations

Maintenant, ceci est différent pour les shells non interactifs , tels que bash -c '<simple command>'. Bien qu’il fork()/exec()s’agisse d’une méthode optimale dans laquelle vous devez traiter de nombreuses commandes, c’est un gaspillage de ressources lorsque vous n’avez qu’une seule commande. Pour citer Stéphane Chazelas de ce post :

Le fork est coûteux, en temps CPU, en mémoire, en descripteurs de fichier alloués ... Avoir un processus shell qui attend de recevoir un autre processus avant de quitter est un gaspillage de ressources. En outre, il est difficile de signaler correctement l'état de sortie du processus distinct qui exécuterait la commande (par exemple, lorsque le processus est supprimé).

Par conséquent, de nombreux shells (pas seulement bash) exec()permettent de permettre à cette commande bash -c ''d'être prise en charge par cette unique commande simple. Et précisément pour les raisons indiquées ci-dessus, il est préférable de minimiser les pipelines dans les scripts shell. Souvent, vous pouvez voir les débutants faire quelque chose comme ceci:

cat /etc/passwd | cut -d ':' -f 6 | grep '/home'

Bien sûr, cela aura fork()3 processus. Ceci est un exemple simple, mais considérons un fichier volumineux, dans la plage de gigaoctets. Ce serait beaucoup plus efficace avec un processus:

awk -F':' '$6~"/home"{print $6}' /etc/passwd

Le gaspillage de ressources peut en réalité être une forme d’attaque par déni de service, et en particulier des bombes fourchettes sont créées via des fonctions de shell qui s’appellent elles-mêmes dans le pipeline, ce qui leur donne plusieurs copies. De nos jours, cela est atténué par la limitation du nombre maximal de processus dans les groupes de contrôle sur systemd , qu'Ubuntu utilise également depuis la version 15.04.

Bien sûr, cela ne signifie pas que bricoler est simplement mauvais. C’est toujours un mécanisme utile, comme indiqué précédemment, mais si vous pouvez vous en tirer avec moins de processus, consécutivement moins de ressources et donc de meilleures performances, évitez-les fork()si possible.

Voir également


1

Pour chaque commande (exemple: grep) que vous exécutez à l'invite bash, vous avez réellement l'intention de démarrer un nouveau processus, puis de revenir à l'invite bash après son exécution.

Si le processus shell (bash) appelle exec () pour exécuter grep, le processus shell sera remplacé par grep. Grep fonctionnera correctement, mais après exécution, le contrôle ne pourra plus retourner dans le shell car le processus bash est déjà remplacé.

Pour cette raison, bash appelle fork (), ce qui ne remplace pas le processus actuel.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.