Pourquoi avons-nous besoin de créer des nouveaux processus?

95

Sous Unix, chaque fois que nous souhaitons créer un nouveau processus, nous découpons le processus actuel en créant un nouveau processus enfant identique au processus parent. nous faisons ensuite un appel système exec pour remplacer toutes les données du processus parent par celles du nouveau processus.

Pourquoi crée-t-on une copie du processus parent au départ sans créer directement un nouveau processus?

sarthak
la source
2
Voir aussi unix.stackexchange.com/questions/31118/…
Ellen Spertus

Réponses:

61

La réponse courte est: forkest sous Unix parce qu’il était facile de s’intégrer au système existant à l’époque et qu’un système précédent à Berkeley avait utilisé le concept de fourches.

De L'évolution du système de partage du temps Unix (le texte pertinent a été mis en évidence ):

Le contrôle des processus sous sa forme moderne a été conçu et mis en œuvre en quelques jours. Il est étonnant de voir avec quelle facilité il s’intègre dans le système existant; dans le même temps, il est facile de voir comment certaines des caractéristiques légèrement inhabituelles de la conception sont présentes précisément parce qu'elles représentent de petits changements faciles à coder de ce qui existait . Un bon exemple est la séparation des fonctions fork et exec. Le modèle le plus courant pour la création de nouveaux processus implique la spécification d'un programme à exécuter par le processus. sous Unix, un processus forké continue d’exécuter le même programme que son parent jusqu’à ce qu’il exécute un exec explicite. La séparation des fonctions n’est certainement pas propre à Unix, elle était en fait présente dans le système de partage du temps de Berkeley, bien connu de Thompson.. Néanmoins, il semble raisonnable de supposer qu’il existe sous Unix principalement à cause de la facilité avec laquelle fork pourrait être implémenté sans trop changer . Le système gérait déjà plusieurs processus (deux); il y avait une table de processus et les processus ont été permutés entre la mémoire principale et le disque. La mise en œuvre initiale de fork est requise uniquement

1) Extension de la table de processus

2) Ajout d'un appel fork qui a copié le processus actuel dans la zone d'échange de disque, en utilisant les primitives d'E / S d'échange existantes, et apporté quelques ajustements à la table de processus.

En fait, l'appel à la fourchette du PDP-7 nécessitait précisément 27 lignes de code d'assemblage. Bien entendu, d’autres modifications du système d’exploitation et des programmes utilisateurs étaient nécessaires, dont certaines plutôt intéressantes et inattendues. Mais une combinaison fork-exec aurait été considérablement plus compliquée , ne serait-ce que parce que exec en tant que tel n'existait pas; sa fonction était déjà remplie, à l’aide d’IO explicites, par le shell.

Depuis cet article, Unix a évolué. forksuivi de execn’est plus le seul moyen d’exécuter un programme.

  • vfork a été créé pour être un fork plus efficace dans le cas où le nouveau processus a l'intention de faire un exec juste après le fork. Après avoir effectué une vfork, les processus parent et enfant partagent le même espace de données et le processus parent est suspendu jusqu'à ce que le processus enfant exécute un programme ou se ferme.

  • posix_spawn crée un nouveau processus et exécute un fichier en un seul appel système. Il faut un tas de paramètres qui vous permettent de partager de manière sélective les fichiers ouverts de l'appelant et de copier sa disposition du signal et d'autres attributs dans le nouveau processus.

Mark Plotnick
la source
5
Bonne réponse, mais j'ajouterais que vfork ne devrait plus être utilisé. La différence de performance est maintenant marginale et son utilisation peut être dangereuse. Voir cette question SO stackoverflow.com/questions/4856255/…, ce site ewontfix.com/7 et "Programmation Unix avancée" page 299 sur vfork
Raphael Ahrens
4
Les machinations (configuration de la structure de données) requises pour posix_spawn()effectuer les mêmes tâches de replomberie après branchement, qui peuvent être facilement effectuées à l'aide d'un fork()code en ligne, constituent un argument convaincant en faveur d' fork()une utilisation beaucoup plus simple.
Jonathan Leffler
34

[Je vais répéter une partie de ma réponse à partir d' ici .]

Pourquoi ne pas simplement avoir une commande qui crée un nouveau processus à partir de zéro? N'est-il pas absurde et inefficace d'en copier un qui ne sera remplacé que tout de suite?

En fait, cela ne serait probablement pas aussi efficace pour plusieurs raisons:

  1. La « copie » produit par fork()est un peu une abstraction, car le noyau utilise une copie sur écriture du système ; tout ce qui doit être créé est une carte mémoire virtuelle. Si la copie appelle alors immédiatement exec(), la plupart des données qui auraient été copiées si elles avaient été modifiées par l'activité du processus ne doivent jamais être copiées / créées car le processus ne nécessite aucune utilisation.

  2. Divers aspects significatifs du processus enfant (par exemple son environnement) ne doivent pas nécessairement être dupliqués individuellement ou définis en fonction d'une analyse complexe du contexte, etc. Ils sont simplement supposés être identiques à ceux du processus appelant. c'est le système assez intuitif que nous connaissons.

Pour expliquer # 1 un peu plus loin, la mémoire qui est "copiée" mais jamais accédée par la suite n’est jamais vraiment copiée, du moins dans la plupart des cas. Une exception dans ce contexte pourrait être si vous avez créé un processus, puis le processus parent s'est terminé avant que l'enfant ne se remplace lui-même exec(). Je dis pourrait parce qu'une grande partie du parent pourrait être mise en cache si la mémoire disponible est suffisante et je ne sais pas dans quelle mesure cela serait exploité (ce qui dépendrait de la mise en œuvre du système d'exploitation).

Bien sûr, cela ne rend pas à première vue l'utilisation d'une copie plus efficace que celle d'une ardoise vierge - sauf que "l'ardoise vierge" n'est pas littéralement rien, et doit impliquer une attribution. Le système pourrait avoir un modèle de processus vierge / nouveau, générique, qu'il copie de la même manière 1, mais qui ne sauvegarderait alors rien, par rapport à la fourchette de copie sur écriture. Donc, n ° 1 démontre simplement que l'utilisation d'un "nouveau" processus vide ne serait pas plus efficace.

Le point 2 explique pourquoi l'utilisation de la fourche est probablement plus efficace. L'environnement d'un enfant est hérité de son parent, même s'il s'agit d'un exécutable complètement différent. Par exemple, si le processus parent est un shell et l’enfant, un navigateur Web, $HOMEreste identique pour les deux, mais puisque l’un ou l’autre pourrait le changer par la suite, il doit s'agir de deux copies distinctes. Celui de l'enfant est produit par l'original fork().

1. Une stratégie qui n'a peut-être pas beaucoup de sens littéral, mais ce que je veux dire, c'est que créer un processus implique plus que copier son image dans la mémoire à partir d'un disque.

boucle d'or
la source
3
Bien que les deux points soient vrais, ni l'un ni l'autre ne prend en charge la raison pour laquelle la méthode de forking a été choisie au lieu d'exécuter un nouveau processus à partir d'un exécutable donné.
SkyDan
3
Je pense que cela répond à la question. Fork est utilisé car, dans les cas où la création d'un nouveau processus est le moyen le plus efficace, le coût d'utilisation de fork est plutôt trivial (probablement moins de 1% du coût de création du processus). D'autre part, il existe de nombreux endroits où fork est considérablement plus efficace ou beaucoup plus simple qu'une API (comme le traitement des descripteurs de fichiers). La décision prise par Unix était de ne prendre en charge qu'une seule API, simplifiant ainsi la spécification.
Cort Ammon
1
@SkyDan Vous avez raison, c'est plutôt une réponse à pourquoi pas qu'à pourquoi , à laquelle Mark Plotnick répond plus directement - ce qui, selon moi, ne signifie pas seulement que c'était le choix le plus facile, mais aussi que c'était probablement le plus efficace. choix (selon la citation de Dennis Richie: "L’appel à la fourchette du PDP-7 nécessitait exactement 27 lignes d’assemblage ... Le système n’existait pas en tant que tel; sa fonction était déjà remplie"). Donc, ce "pourquoi pas" est en fait une réflexion sur deux stratégies dans lesquelles une apparaît superficiellement plus simple et plus efficace, alors que ce n’est peut-être pas le cas (témoin du destin douteux de ...
goldilocks
1
Goldilocks est correct. Il existe des situations où il est meilleur marché de forger et de modifier que d'en créer un nouveau à partir de zéro. L'exemple le plus extrême, bien sûr, est le moment où vous voulez un comportement de fork. fork()peut le faire très rapidement (comme l'a mentionné GL, de l'ordre de 27 lignes de montage). Si vous voulez créer un processus en partant de zéro, cela fork()ne coûte qu'un tout petit peu plus que de partir d'un processus créé vide (27 lignes d'assemblage + coût de la fermeture des descripteurs de fichiers). Donc forkgère à la fois fork et créer bien, alors que createpeut seulement gérer créer bien.
Cort Ammon
2
Votre réponse a trait aux améliorations matérielles: mémoire virtuelle, copie sur écriture. Avant cela, forkcopiez en fait toute la mémoire de processus et cela coûtait très cher.
Barmar
6

Je pense que la raison pour laquelle Unix n'avait que la forkfonction de créer de nouveaux processus est le résultat de la philosophie Unix

Ils construisent une fonction qui fait une chose bien. Cela crée un processus enfant.

Ce que l’on fait avec le nouveau processus appartient alors au programmeur. Il peut utiliser l'une des exec*fonctions et lancer un programme différent, ou il ne peut pas utiliser exec et utiliser les deux instances du même programme, ce qui peut être utile.

Donc, vous obtenez un plus grand degré de liberté puisque vous pouvez utiliser

  1. fourche sans exec *
  2. fourchette avec exec * ou
  3. juste exec * sans fourche

et en plus vous suffit de mémoriser les forket les exec*appels de fonction, que vous deviez faire dans les années 1970.

Raphael Ahrens
la source
3
Je comprends comment fonctionnent les fourches et comment les utiliser. Mais pourquoi voudrais-je créer un nouveau processus, alors que je peux faire la même chose mais avec moins d’effort? Par exemple, mon professeur m'a confié une tâche dans laquelle je devais créer un processus pour chaque nombre transmis à argv, afin de vérifier si le nombre était un nombre premier. Mais n'est-ce pas simplement un détour de faire finalement la même chose? J'aurais pu simplement utiliser un tableau et utiliser une fonction pour chaque nombre ... Alors, pourquoi créons-nous des processus enfants au lieu d'effectuer tous les traitements dans le processus principal?
user1534664
2
Je me risquerais à dire que vous comprenez comment fonctionnent les forks et comment les utiliser, car vous avez déjà eu un enseignant qui vous a confié une tâche dans laquelle vous deviez créer un ensemble de processus (avec le nombre spécifié au moment de l'exécution), contrôlez-les, coordonnez-les et communiquez entre eux. Bien sûr, personne ne voudrait faire une chose aussi anodine que cela dans la vie réelle. Toutefois, si vous rencontrez un problème volumineux qui peut facilement être décomposé en éléments pouvant être traités en parallèle (par exemple, la détection des contours dans une image), la conversion en bande vous permet d’utiliser plusieurs cœurs de processeur simultanément.
Scott
5

Il existe deux philosophies de création de processus: fourchette avec héritage et création avec arguments. Unix utilise fork, évidemment. (OSE, par exemple, et VMS utilisent la méthode create.) Unix a BEAUCOUP de caractéristiques pouvant être héritées, et d'autres sont ajoutées périodiquement. Par héritage, ces nouvelles caractéristiques peuvent être ajoutées SANS MODIFIER LES PROGRAMMES EXISTANTS! À l'aide d'un modèle de création avec arguments, l'ajout de nouvelles caractéristiques signifierait l'ajout de nouveaux arguments à l'appel de création. Le modèle Unix est plus simple.

Il offre également le modèle très utile fork-without-exec, dans lequel un processus peut se diviser en plusieurs parties. Cela était essentiel à l'époque où il n'existait aucune forme d'E / S asynchrone, ce qui est utile pour tirer parti de plusieurs processeurs dans un système. (Pré-discussions.) J'ai souvent fait cela au fil des ans, même récemment. En substance, cela permet de conteneuriser plusieurs "programmes" dans un seul programme, de sorte qu'il n'y a absolument aucune place pour la corruption ou les asymétries de version, etc.

Le modèle fork / exec permet également à un enfant spécifique d'hériter d'un environnement radicalement étrange, configuré entre le fork et l'exec. Des choses comme les descripteurs de fichiers hérités, en particulier. (Une extension de stdio fd.) Le modèle de création n'offre pas la possibilité d'hériter de tout ce qui n'a pas été envisagé par les créateurs de l'appel de création.

Certains systèmes peuvent également prendre en charge la compilation dynamique de code natif, où le processus écrit son propre programme de code natif. En d'autres termes, il veut un nouveau programme qu'il écrit lui-même à la volée, SANS passer par le cycle du code source / compilateur / éditeur de liens et occupant de l'espace disque. (Je pense qu’il existe un système de langage Verilog qui le fait.) Le modèle fork prend en charge cela, mais pas le modèle create.

Jim Cathey
la source
Les descripteurs de fichier ne sont pas “une extension de stdio”; Les pointeurs de fichiers stdio sont un wrapper autour des descripteurs de fichiers. Les descripteurs de fichier sont arrivés en premier, et ce sont les descripteurs d'E / S Unix fondamentaux. Mais, sinon, c'est un bon point.
Scott
2

La fonction fork () ne sert pas uniquement à copier le processus père, elle renvoie une valeur indiquant que le processus est le processus père ou fils, l'image ci-dessous explique comment utiliser four () comme père et fils:

entrez la description de l'image ici

comme indiqué lorsque le processus est le père fork () renvoie l'ID du processus fils, PID sinon il retourne0

par exemple, vous pouvez l'utiliser si vous avez un processus (serveur Web) qui reçoit les demandes et à chaque demande, il crée un son processprocessus pour traiter cette demande, ici le père et ses fils ont des emplois différents.

SO, pas d'exécuter une copie d'un processus n'est pas la chose exacte comme fork ().

Networker
la source
5
Bien que ce soit vrai, cela ne répond pas à la question. Pourquoi est-il nécessaire de forger pour la création de processus si je veux exécuter un exécutable différent?
SkyDan
1
Je suis d'accord avec SkyDan - cela ne répond pas à la question. posix_spawn est une version un peu plus sophistiquée de ce que l'on aurait pu imaginer il y a 30 ans (avant Posix) sous la forme d'une fonction fork_execve ; celui qui crée un nouveau processus, initialise son image à partir d’un fichier exécutable, sans même laisser entendre que le processus parent doit être copié (à l’exception de la liste des arguments, de l’environnement et des attributs du processus (par exemple, le répertoire de travail)), et renvoie le PID du nouveau processus à l'appelant (processus parent) .
Scott
1
Il existe d'autres moyens de transmettre des informations «parentales» à un enfant. La technique de la valeur de retour s'avère être le moyen le plus efficace de procéder fork si vous supposez que vous voulez forken premier lieu
Cort Ammon
0

La redirection d'E / S est plus facilement implémentée après fork et avant exec. L'enfant, sachant que c'est l'enfant, peut fermer les descripteurs de fichier, en ouvrir de nouveaux, dup () ou dup2 () pour les placer sur le bon numéro fd, etc., sans affecter le parent. Après cela, et éventuellement toute modification de la variable d’environnement souhaitée (n’affectant pas non plus le parent), il peut exécuter le nouveau programme dans l’environnement sur mesure.

Richard Hamilton
la source
Tout ce que vous faites ici est de répéter le troisième paragraphe de la réponse de Jim Cathey avec un peu plus de détails.
Scott
-2

Je pense que tout le monde ici sait que fork fonctionne, mais la question est de savoir pourquoi nous devons créer une copie exacte du parent en utilisant fork. Réponse ==> Prenons un exemple de serveur (sans fork), pendant que le client-1 accède au serveur, si le deuxième client-2 est arrivé en même temps et veut accéder au serveur mais le serveur ne donne pas l’autorisation aux nouveaux arrivés. client-2 parce que le serveur est occupé à servir le client-1, le client-2 doit donc attendre. Une fois que tous les services du client-1 sont terminés, le client-2 est maintenant en mesure d'accéder au serveur.Maintenant, considérez si client-3 arrive, le client-3 doit donc attendre que tous les services du client-2 soient terminés. Prenez le scénario où des milliers de clients doivent accéder au serveur en même temps ... puis tous les clients doivent attendez (le serveur est occupé !!).

Ceci est évité en créant (à l'aide de fork) une copie exacte en double (par exemple, enfant) du serveur, où chaque enfant (copie exacte en double de son serveur parent) est dédié au client nouvellement arrivé, de sorte que tous les clients accèdent simultanément au même serveur. serveur.

Harshil Mania
la source
C'est pourquoi les processus de serveur ne doivent pas être à une seule unité d'exécution, car ils gèrent les demandes des clients de manière consécutive lorsqu'ils peuvent être traités simultanément - par exemple, dans des processus distincts. Mais le modèle de serveur multithread peut facilement être implémenté avec un processus d'écoute qui accepte les demandes des clients et crée un nouveau processus dans lequel exécuter le programme client-service. Le seul avantage offert par l' forkappel qui copie le processus parent est qu'il n'est pas nécessaire de disposer de deux programmes distincts - mais le fait de disposer de programmes distincts (par exemple, inetd) peut rendre le système plus modulaire.
Scott