Quelle est la raison d'effectuer un double fork lors de la création d'un démon?

165

J'essaye de créer un démon en python. J'ai trouvé la question suivante , qui contient de bonnes ressources que je suis actuellement, mais je suis curieux de savoir pourquoi un double fork est nécessaire. J'ai parcouru google et trouvé de nombreuses ressources déclarant qu'il en fallait une, mais pas pourquoi.

Certains mentionnent que c'est pour empêcher le démon d'acquérir un terminal de contrôle. Comment ferait-il cela sans la deuxième fourche? Quelles sont les répercussions?

Shabbyrobe
la source
2
Une difficulté avec un double fork est que le parent ne peut pas facilement obtenir le PID du processus petit-enfant (l' fork()appel renvoie le PID de l'enfant au parent, il est donc facile d'obtenir le PID du processus enfant, mais pas si facile à obtenir le PID du processus petit - enfant ).
Craig McQueen

Réponses:

105

En regardant le code référencé dans la question, la justification est:

Fourchez un deuxième enfant et sortez immédiatement pour éviter les zombies. Cela rend le deuxième processus enfant orphelin, ce qui rend le processus init responsable de son nettoyage. Et, puisque le premier enfant est un chef de session sans terminal de contrôle, il lui est possible d'en acquérir un en ouvrant un terminal dans le futur (systèmes basés sur System V). Ce deuxième fork garantit que l'enfant n'est plus un chef de session, empêchant le démon d'acquérir un terminal de contrôle.

Il s'agit donc de s'assurer que le démon est re-parenté sur init (juste au cas où le processus de lancement du démon dure longtemps), et supprime toute chance que le démon réacquiert un tty de contrôle. Donc, si aucun de ces cas ne s'applique, alors un fork devrait être suffisant. " Programmation réseau Unix - Stevens " a une bonne section à ce sujet.

Beano
la source
28
Ce n'est pas tout à fait correct. La manière standard de créer un démon est de simplement faire p=fork(); if(p) exit(); setsid(). Dans ce cas, le parent quitte également et le premier processus enfant est reparenté. La magie double fourche est uniquement nécessaire pour empêcher le démon de l' acquisition d' un téléscripteur.
parasietje
1
Donc, si je comprends bien, si mon programme démarre et forksun childprocessus, ce tout premier processus enfant sera un session leaderet pourra ouvrir un terminal TTY. Mais si je bifurque à nouveau à partir de cet enfant et termine ce premier enfant, le deuxième enfant fourchu ne sera pas un session leaderet ne pourra pas ouvrir un terminal TTY. Cette affirmation est-elle correcte?
tonix
2
@tonix: simplement forger ne crée pas de chef de session. Cela se fait par setsid(). Ainsi, le premier processus forké devient un chef de session après avoir appelé setsid(), puis nous bifurquons à nouveau pour que le processus final à double fourche ne soit plus un leader de session. Outre l'exigence d' setsid()être un animateur de session, vous êtes sur place.
dbmikus
169

J'essayais de comprendre la double fourchette et je suis tombé sur cette question ici. Après de nombreuses recherches, c'est ce que j'ai compris. Espérons que cela aidera à clarifier les choses pour quiconque a la même question.

Sous Unix, chaque processus appartient à un groupe qui à son tour appartient à une session. Voici la hiérarchie…

Session (SID) → Groupe de processus (PGID) → Processus (PID)

Le premier processus du groupe de processus devient le chef de groupe de processus et le premier processus de la session devient le chef de session. Chaque session peut être associée à un TTY. Seul un responsable de session peut prendre le contrôle d'un TTY. Pour qu'un processus soit véritablement démonisé (exécuté en arrière-plan), nous devons nous assurer que le chef de session est tué afin qu'il n'y ait aucune possibilité que la session prenne jamais le contrôle du TTY.

J'ai exécuté le programme d'exemple de démon python de Sander Marechal à partir de ce site sur mon Ubuntu. Voici les résultats avec mes commentaires.

1. `Parent`    = PID: 28084, PGID: 28084, SID: 28046
2. `Fork#1`    = PID: 28085, PGID: 28084, SID: 28046
3. `Decouple#1`= PID: 28085, PGID: 28085, SID: 28085
4. `Fork#2`    = PID: 28086, PGID: 28085, SID: 28085

Notez que le processus est le chef de session après Decouple#1, car c'est PID = SID. Il pourrait encore prendre le contrôle d'un TTY.

Notez que ce Fork#2n'est plus le chef de session PID != SID. Ce processus ne peut jamais prendre le contrôle d'un TTY. Vraiment démonisé.

Personnellement, je trouve que la terminologie fork-two prête à confusion. Un meilleur idiome pourrait être fork-decouple-fork.

Liens d'intérêt supplémentaires:

Praveen Gollakota
la source
Forking deux fois empêche également la création de zombies lorsque le processus parent s'exécute plus longtemps et pour une raison quelconque, la suppression du gestionnaire par défaut pour le signal informant que le processus est mort.
Trismegistos
Mais deuxièmement, il peut également appeler le découplage et devenir chef de session, puis acquérir un terminal.
Trismegistos
2
Ce n'est pas vrai. Le premier fork()empêche déjà de créer des zombies, à condition de fermer le parent.
parasietje
1
Un exemple minimal pour produire les résultats cités ci-dessus: gist.github.com/cannium/7aa58f13c834920bb32c
can.
1
Serait-il bon d'appeler setsid() avant un seul fork()? En fait, je suppose que les réponses à cette question répondent à cela.
Craig McQueen
118

Strictement parlant, le double fork n'a rien à voir avec la re-parentalité du démon en tant qu'enfant de init. Tout ce qui est nécessaire pour re-parent l'enfant est que le parent doit quitter. Cela peut être fait avec une seule fourchette. De plus, faire un double fork en lui-même ne renvoie pas le processus démon à init; le parent du démon doit quitter. En d'autres termes, le parent se ferme toujours lors de la création d'un démon approprié pour que le processus démon soit re-parenté init.

Alors pourquoi la double fourchette? POSIX.1-2008 Section 11.1.3, « Le terminal de contrôle », a la réponse (italiques ajoutés):

Le terminal de contrôle pour une session est alloué par le chef de session d'une manière définie par l' implémentation. Si un responsable de session n'a pas de terminal de contrôle et ouvre un fichier de périphérique de terminal qui n'est pas déjà associé à une session sans utiliser l' O_NOCTTYoption (voir open()), il est défini par l'implémentation si le terminal devient le terminal de contrôle du leader de session. Si un processus qui n'est pas un chef de session ouvre un fichier de terminal, ou si l' O_NOCTTYoption est activée open(), ce terminal ne doit pas devenir le terminal de contrôle du processus appelant .

Cela nous indique que si un processus démon fait quelque chose comme ça ...

int fd = open("/dev/console", O_RDWR);

... alors le processus démon peut devenir /dev/consoleson terminal de contrôle, selon que le processus démon est un chef de session et selon l'implémentation du système. Le programme peut garantir que l'appel ci-dessus n'acquiert pas un terminal de contrôle si le programme s'assure d'abord qu'il n'est pas un chef de session.

Normalement, lors du lancement d'un démon, il setsidest appelé (depuis le processus enfant après l'appel fork) pour dissocier le démon de son terminal de contrôle. Cependant, appeler setsidsignifie également que le processus appelant sera le chef de session de la nouvelle session, ce qui laisse ouverte la possibilité que le démon puisse réacquérir un terminal de contrôle. La technique du double fork garantit que le processus démon n'est pas le leader de la session, ce qui garantit alors qu'un appel à open, comme dans l'exemple ci-dessus, n'entraînera pas la réacquisition par le processus démon d'un terminal de contrôle.

La technique du double fourchette est un peu paranoïaque. Cela peut ne pas être nécessaire si vous savez que le démon n'ouvrira jamais un fichier de périphérique terminal. De plus, sur certains systèmes, cela peut ne pas être nécessaire même si le démon ouvre un fichier de périphérique terminal, car ce comportement est défini par l'implémentation. Cependant, une chose qui n'est pas définie par l'implémentation est que seul un chef de session peut allouer le terminal de contrôle. Si un processus n'est pas un chef de session, il ne peut pas allouer de terminal de contrôle. Par conséquent, si vous voulez être paranoïaque et être certain que le processus démon ne peut pas acquérir par inadvertance un terminal de contrôle, quelles que soient les spécificités définies par l'implémentation, alors la technique du double fork est essentielle.

Moulage Dan
la source
3
+1 Dommage que cette réponse soit venue ~ quatre ans après que la question ait été posée.
Tim Seguine le
12
Mais cela n'explique toujours pas pourquoi il est si terriblement important qu'un démon ne puisse pas réacquérir un terminal de contrôle
UloPe
7
Le mot-clé est «par inadvertance» acquérir un terminal de contrôle. Si le processus ouvre un terminal et qu'il devient le processus contrôlant le terminal, que si quelqu'un émet un ^ C à partir de ce terminal, il pourrait terminer le processus. Il serait donc peut-être intéressant de protéger un processus pour éviter que cela ne se produise par inadvertance. Personnellement, je m'en tiendrai à un seul fork et setsid () pour le code que j'écris et que je sais n'ouvrira pas les terminaux.
BobDoolittle
1
@BobDoolittle comment cela peut-il arriver "par inadvertance"? Un processus ne finira pas forcément par ouvrir des terminaux s'il n'est pas écrit pour le faire. Peut-être que le double forking est utile si vous, le programmeur, ne connaissez pas le code et ne savez pas s'il pourrait ouvrir un tty.
Marius
10
@Marius Imaginez ce qui pourrait arriver si vous ajoutez une ligne comme celui - ci dans le fichier de configuration de votre démon: LogFile=/dev/console. Les programmes n'ont pas toujours le contrôle à la compilation des fichiers qu'ils peuvent ouvrir;)
Dan Molding
11

Tiré de Bad CTK :

«Sur certaines versions d'Unix, vous êtes obligé de faire un double fork au démarrage, afin de passer en mode démon. C'est parce que le forking simple n'est pas garanti de se détacher du terminal de contrôle.

Stefan Thyberg
la source
3
Comment une fourche simple ne peut-elle pas se détacher du terminal de commande mais une fourche double peut-elle le faire? Sur quels unix cela se produit-il?
bdonlan
12
Un démon doit fermer ses descripteurs de fichier d'entrée et de sortie (fds), sinon il serait toujours attaché au terminal dans lequel il a été démarré. Un processus en fourche hérite de ceux du parent. Apparemment, le premier enfant ferme le fds mais cela ne nettoie pas tout. Sur le deuxième fork, les fds n'existent pas, donc le deuxième enfant ne peut plus être connecté à quoi que ce soit.
Aaron Digulla
4
@Aaron: Non, un démon se "détache" correctement de son terminal de contrôle en appelant setsidaprès un fork initial. Il s'assure ensuite qu'il reste détaché d'un terminal de contrôle en forçant à nouveau et en faisant setsidquitter le chef de session (le processus qui a appelé ).
Dan Moulding
2
@bdonlan: Ce n'est pas forkcela qui se détache du terminal de contrôle. C'est setsidça qui le fait. Mais setsidéchouera s'il est appelé par un chef de groupe de processus. Il forkfaut donc faire une première étape avant setsidde s'assurer que setsidc'est appelé à partir d'un processus qui n'est pas un chef de groupe de processus. Le second forkgarantit que le processus final (celui qui sera le démon) n'est pas un chef de session. Seuls les chefs de session peuvent acquérir un terminal de contrôle, donc ce deuxième fork garantit que le démon ne réacquiert pas par inadvertance un terminal de contrôle. Cela est vrai pour tout système d'exploitation POSIX.
Dan Moulding
@DanMoulding Cela ne garantit pas que le second enfant n'acquiert pas le terminal de contrôle car il peut appeler setsid et devenir le chef de session, puis acquérir le terminal de contrôle.
Trismegistos
7

Selon "Programmation avancée dans l'environnement Unix", par Stephens et Rago, le deuxième fork est plus une recommandation, et il est fait pour garantir que le démon n'acquiert pas de terminal de contrôle sur les systèmes basés sur System V.

Paolo Tedesco
la source
3

Une des raisons est que le processus parent peut immédiatement attendre_pid () pour l'enfant, puis l'oublier. Quand le petit-enfant meurt, son parent est init, et il attendra () pour cela - et le sortira de l'état zombie.

Le résultat est que le processus parent n'a pas besoin de connaître les enfants fourchus, et il permet également de dériver des processus longs à partir de bibliothèques, etc.

KarlP
la source
2

L'appel daemon () a l'appel parent _exit () s'il réussit. La motivation initiale a peut-être été de permettre au parent de faire un travail supplémentaire pendant que l'enfant démonise.

Cela peut aussi être basé sur une croyance erronée selon laquelle c'est nécessaire pour s'assurer que le démon n'a pas de processus parent et qu'il est reparenté à init - mais cela se produira de toute façon une fois que le parent meurt dans le cas de la fourche unique.

Donc, je suppose que tout se résume à la tradition à la fin - une seule fourchette est suffisante tant que le parent meurt de toute façon rapidement.

bdonlan
la source
2

Une discussion décente à ce sujet semble être à l' adresse http://www.developerweb.net/forum/showthread.php?t=3025

Citant mlampkin à partir de là:

... pensez à l'appel setsid () comme la "nouvelle" façon de faire les choses (se dissocier du terminal) et le [second] appel fork () après lui comme une redondance pour traiter le SVr4 ...

Stobor
la source
-1

Cela pourrait être plus facile à comprendre de cette manière:

  • Le premier fork et setsid créeront une nouvelle session (mais l'ID de processus == ID de session).
  • Le deuxième fork s'assure que l'ID de processus! = ID de session.
pandy.song
la source