Was ist der Grund für die Ausführung einer Doppelgabelung beim Erstellen eines Dämons?

165

Ich versuche einen Daemon in Python zu erstellen. Ich habe die folgende Frage gefunden , die einige gute Ressourcen enthält, denen ich derzeit folge, aber ich bin gespannt, warum eine Doppelgabel notwendig ist. Ich habe mich bei Google umgesehen und viele Ressourcen gefunden, die erklären, dass eine notwendig ist, aber nicht warum.

Einige erwähnen, dass dies verhindern soll, dass der Dämon ein steuerndes Terminal erhält. Wie würde es das ohne die zweite Gabel machen? Was sind die Auswirkungen?

Shabbyrobe
quelle
2
Eine Schwierigkeit bei der Ausführung einer Doppelgabelung besteht darin, dass der Elternteil die PID des Enkelkindprozesses nicht einfach fork()abrufen kann (der Aufruf gibt die PID des Kindes an den Elternteil zurück, sodass es einfach ist, die PID des untergeordneten Prozesses abzurufen, dies jedoch nicht so einfach ist Holen Sie sich die PID des Enkelprozesses ).
Craig McQueen

Antworten:

105

Mit Blick auf den Code, auf den in der Frage verwiesen wird, lautet die Begründung:

Gabel ein zweites Kind und gehe sofort, um Zombies zu verhindern. Dies führt dazu, dass der zweite untergeordnete Prozess verwaist ist und der Init-Prozess für seine Bereinigung verantwortlich ist. Und da das erste Kind ein Sitzungsleiter ohne steuerndes Terminal ist, kann es eines erwerben, indem es in Zukunft ein Terminal öffnet (System V-basierte Systeme). Diese zweite Abzweigung garantiert, dass das Kind kein Sitzungsleiter mehr ist, und verhindert, dass der Dämon jemals ein steuerndes Terminal erhält.

Es muss also sichergestellt werden, dass der Dämon erneut auf init übergeordnet wird (nur für den Fall, dass der Prozess, der den Dämon startet, langlebig ist), und es wird jede Chance ausgeschlossen, dass der Dämon wieder eine steuernde Tty erhält. Wenn also keiner dieser Fälle zutrifft, sollte eine Gabel ausreichen. " Unix Network Programming - Stevens " hat einen guten Abschnitt dazu.

Beano
quelle
28
Das ist nicht ganz richtig. Die Standardmethode zum Erstellen eines Daemons besteht darin, dies einfach zu tun p=fork(); if(p) exit(); setsid(). In diesem Fall wird auch der Elternteil beendet und der erste untergeordnete Prozess wird repariert. Die Doppelgabel-Magie ist nur erforderlich, um zu verhindern, dass der Dämon einen tty erhält.
Parasietje
1
So wie ich es verstehe, wird, wenn mein Programm startet und forksein childProzess, dieser allererste untergeordnete Prozess session leaderein TTY-Terminal sein und in der Lage sein, ihn zu öffnen. Aber wenn ich mich wieder von diesem Kind abkehre und dieses erste Kind beende, ist das zweite gegabelte Kind kein session leaderund kann kein TTY-Terminal öffnen. Ist diese Aussage richtig?
Tonix
2
@tonix: Durch einfaches Gabeln wird kein Sitzungsleiter erstellt. Das macht das von setsid(). Der erste gegabelte Prozess wird also nach dem Aufruf zum Sitzungsleiter, setsid()und dann werden wir erneut gegabelt, sodass der letzte, doppelt gegabelte Prozess kein Sitzungsleiter mehr ist. Abgesehen von der Anforderung setsid(), ein Sitzungsleiter zu sein, sind Sie genau richtig.
Dbmikus
169

Ich habe versucht, die Doppelgabel zu verstehen und bin hier auf diese Frage gestoßen. Nach vielen Recherchen habe ich das herausgefunden. Hoffentlich hilft es, die Dinge für jeden, der die gleiche Frage hat, besser zu klären.

Unter Unix gehört jeder Prozess zu einer Gruppe, die wiederum zu einer Sitzung gehört. Hier ist die Hierarchie ...

Sitzung (SID) → Prozessgruppe (PGID) → Prozess (PID)

Der erste Prozess in der Prozessgruppe wird zum Prozessgruppenleiter und der erste Prozess in der Sitzung zum Sitzungsleiter. Jeder Sitzung kann ein TTY zugeordnet sein. Nur ein Sitzungsleiter kann die Kontrolle über ein TTY übernehmen. Damit ein Prozess wirklich dämonisiert wird (im Hintergrund ausgeführt wird), sollten wir sicherstellen, dass der Sitzungsleiter getötet wird, damit die Sitzung niemals die Kontrolle über das TTY übernehmen kann.

Ich habe Sander Marechals Python-Beispiel-Daemon-Programm von dieser Site auf meinem Ubuntu ausgeführt. Hier sind die Ergebnisse mit meinen Kommentaren.

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

Beachten Sie, dass der Prozess nachher der Sitzungsleiter ist Decouple#1, da dies der Fall ist PID = SID. Es könnte immer noch die Kontrolle über ein TTY übernehmen.

Beachten Sie, dass dies Fork#2nicht mehr der Sitzungsleiter ist PID != SID. Dieser Prozess kann niemals die Kontrolle über ein TTY übernehmen. Wirklich dämonisiert.

Ich persönlich finde die Terminologie zweimal verwirrend. Eine bessere Redewendung könnte Gabel-Entkopplungs-Gabel sein.

Zusätzliche interessante Links:

Praveen Gollakota
quelle
Durch zweimaliges Gabeln wird auch verhindert, dass Zombies erstellt werden, wenn der übergeordnete Prozess länger ausgeführt wird, und aus irgendeinem Grund wird der Standardhandler entfernt, um zu signalisieren, dass der Prozess beendet ist.
Trismegistos
Aber zweitens kann auch entkoppeln und Sitzungsleiter werden und dann Terminal erwerben.
Trismegistos
2
Das ist nicht wahr. Das erste fork()verhindert bereits das Erstellen von Zombies, vorausgesetzt, Sie schließen das übergeordnete Element.
Parasietje
1
Ein minimales Beispiel für die oben genannten Ergebnisse: gist.github.com/cannium/7aa58f13c834920bb32c
can.
1
Wäre es gut, setsid() vor einer Single anzurufen fork()? Eigentlich denke ich, dass die Antworten auf diese Frage das beantworten.
Craig McQueen
117

Genau genommen hat die Doppelgabel nichts damit zu tun, den Dämon als Kind von neu zu erziehen init. Alles, was notwendig ist, um das Kind wieder zu erziehen, ist, dass das Elternteil beendet werden muss. Dies kann mit nur einer Gabel erfolgen. Wenn Sie einen Double-Fork alleine ausführen, wird der Daemon-Prozess nicht erneut übergeordnet init. Das übergeordnete Element des Dämons muss beendet werden . Mit anderen Worten, das übergeordnete Element wird immer beendet, wenn ein geeigneter Dämon gegabelt wird, sodass der Dämonprozess erneut übergeordnet wird init.

Warum also die Doppelgabel? POSIX.1-2008 Abschnitt 11.1.3, " Das steuernde Terminal ", enthält die Antwort (Hervorhebung hinzugefügt):

Das steuernde Terminal für eine Sitzung wird vom Sitzungsleiter auf implementierungsdefinierte Weise zugewiesen . Wenn ein Sitzungsleiter kein steuerndes Terminal hat und eine Terminalgerätedatei öffnet, die noch keiner Sitzung zugeordnet ist, ohne die O_NOCTTYOption zu verwenden (siehe open()), wird durch die Implementierung definiert, ob das Terminal zum steuernden Terminal des Sitzungsleiters wird. Wenn ein Prozess, der kein Sitzungsleiter ist, eine Terminaldatei öffnet oder die O_NOCTTYOption verwendet wird open(), wird dieses Terminal nicht zum steuernden Terminal des aufrufenden Prozesses .

Dies sagt uns, dass, wenn ein Daemon-Prozess so etwas tut ...

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

... dann wird der Daemon - Prozess könnte erwerben /dev/consoleals Steueranschluss, je nachdem , ob der Daemon - Prozess ist ein Session - Leiter, und in Abhängigkeit von der Systemimplementierung. Das Programm kann garantieren, dass der obige Aufruf kein steuerndes Terminal erhält, wenn das Programm zuerst sicherstellt, dass es kein Sitzungsleiter ist.

Normalerweise wird beim Starten eines Dämons setsid(vom untergeordneten Prozess nach dem Aufruf fork) aufgerufen , um den Dämon von seinem steuernden Terminal zu trennen. Aufrufen setsidbedeutet jedoch auch, dass der aufrufende Prozess der Sitzungsleiter der neuen Sitzung ist, wodurch die Möglichkeit offen bleibt, dass der Dämon ein steuerndes Terminal erneut abrufen kann. Die Double-Fork-Technik stellt sicher, dass der Daemon-Prozess nicht der Sitzungsleiter ist, wodurch garantiert wird, dass ein Aufruf von open, wie im obigen Beispiel, nicht dazu führt, dass der Daemon-Prozess ein steuerndes Terminal erneut erhält.

Die Doppelgabeltechnik ist etwas paranoid. Dies ist möglicherweise nicht erforderlich, wenn Sie wissen, dass der Dämon niemals eine Endgerätedatei öffnet. Auf einigen Systemen ist dies möglicherweise auch dann nicht erforderlich, wenn der Dämon eine Endgerätedatei öffnet, da dieses Verhalten durch die Implementierung definiert ist. Eine nicht implementierungsdefinierte Sache ist jedoch, dass nur ein Sitzungsleiter das steuernde Terminal zuweisen kann. Wenn ein Prozess kein Sitzungsleiter ist, kann er kein steuerndes Terminal zuweisen. Wenn Sie also paranoid sein möchten und sicher sein möchten, dass der Daemon-Prozess unabhängig von implementierungsdefinierten Besonderheiten nicht versehentlich ein steuerndes Terminal erwerben kann, ist die Double-Fork-Technik unerlässlich.

Dan Moulding
quelle
3
+1 Schade, dass diese Antwort ~ vier Jahre nach der Frage kam.
Tim Seguine
12
Aber das erklärt immer noch nicht, warum es so schrecklich wichtig ist, dass ein Dämon ein steuerndes Terminal nicht wieder
erwerben
7
Das Schlüsselwort lautet "versehentlich" ein steuerndes Terminal erwerben. Wenn der Prozess zufällig ein Terminal öffnet und es zum Prozesssteuerungsterminal wird, kann der Prozess beendet werden, wenn jemand ein ^ C von diesem Terminal aus ausgibt. Es könnte also hilfreich sein, einen Prozess davor zu schützen, dass ihm dies versehentlich passiert. Persönlich bleibe ich bei einer einzelnen Gabel und setsid () für Code, den ich schreibe und von dem ich weiß, dass er keine Terminals öffnet.
BobDoolittle
1
@ BobDoolittle wie könnte das "versehentlich" passieren? Ein Prozess wird nicht einfach so oder so Terminals öffnen, wenn er nicht dafür geschrieben wurde. Vielleicht ist Double-Forking nützlich, wenn Sie als Programmierer den Code nicht kennen und nicht wissen, ob er möglicherweise ein tty öffnet.
Marius
10
@Marius Stellen Sie sich vor, was passieren könnte, wenn Sie der Konfigurationsdatei Ihres Daemons eine Zeile wie diese hinzufügen : LogFile=/dev/console. Programme haben nicht immer die Kontrolle über die Kompilierungszeit, welche Dateien sie öffnen könnten;)
Dan Moulding
11

Entnommen aus Bad CTK :

"Bei einigen Unix-Versionen müssen Sie beim Start eine Doppelgabelung ausführen, um in den Dämonmodus zu wechseln. Dies liegt daran, dass sich die Einzelgabelung nicht garantiert vom steuernden Terminal löst."

Stefan Thyberg
quelle
3
Wie kann sich eine Einzelgabel nicht vom Steuerterminal lösen, sondern eine Doppelgabel? Auf welchen Unixen tritt dies auf?
Bdonlan
12
Ein Daemon muss seine Eingabe- und Ausgabedateideskriptoren (fds) schließen, andernfalls wird er weiterhin an das Terminal angehängt, in dem er gestartet wurde. Ein gegabelter Prozess erbt diese vom übergeordneten Element. Anscheinend schließt das erste Kind die FDS, aber das räumt nicht alles auf. Auf der zweiten Gabelung existieren die fds nicht, so dass das zweite Kind nicht mehr mit irgendetwas verbunden werden kann.
Aaron Digulla
4
@Aaron: Nein, ein Daemon "löst" sich ordnungsgemäß von seinem steuernden Terminal, indem er setsidnach einer anfänglichen Abzweigung aufruft . Anschließend wird sichergestellt, dass es von einem steuernden Terminal getrennt bleibt, indem es erneut gegabelt wird und der Sitzungsleiter (der aufgerufene Prozess setsid) beendet wird.
Dan Moulding
2
@bdonlan: Es ist nicht so, forkdass es sich vom steuernden Terminal löst. Das setsidmacht es. Aber setsidwird scheitern , wenn es aus einem Prozessgruppenleiter genannt wird. Daher forkmuss zuvor eine Initiale erstellt werden, setsidum sicherzustellen, dass setsidsie von einem Prozess aufgerufen wird, der kein Prozessgruppenleiter ist. Der zweite forkstellt sicher, dass der letzte Prozess (derjenige, der der Dämon sein wird) kein Sitzungsleiter ist. Nur Sitzungsleiter können ein steuerndes Terminal erwerben, sodass diese zweite Abzweigung garantiert, dass der Dämon nicht versehentlich ein steuerndes Terminal wiedererlangt. Dies gilt für jedes POSIX-Betriebssystem.
Dan Moulding
@DanMoulding Dies garantiert nicht, dass das zweite Kind kein steuerndes Terminal erhält, da es setsid aufrufen und Sitzungsleiter werden und dann das Steuerungsterminal erwerben kann.
Trismegistos
7

Laut "Advanced Programming in the Unix Environment" von Stephens und Rago ist die zweite Verzweigung eher eine Empfehlung und soll sicherstellen, dass der Dämon auf System V-basierten Systemen kein steuerndes Terminal erhält.

Paolo Tedesco
quelle
3

Ein Grund dafür ist, dass der übergeordnete Prozess sofort auf das untergeordnete Element warten und es dann vergessen kann. Wenn dann das Enkelkind stirbt, ist sein Elternteil init und es wird darauf warten () - und es aus dem Zombie-Zustand entfernen.

Das Ergebnis ist, dass der übergeordnete Prozess die gegabelten untergeordneten Prozesse nicht kennen muss und es auch möglich ist, lang laufende Prozesse aus Bibliotheken usw. zu verzweigen.

KarlP
quelle
2

Der Aufruf von daemon () hat den übergeordneten Aufruf _exit (), wenn er erfolgreich ist. Die ursprüngliche Motivation könnte darin bestanden haben, den Eltern zu erlauben, zusätzliche Arbeit zu leisten, während das Kind dämonisiert.

Es kann auch auf einer falschen Annahme beruhen, dass dies notwendig ist, um sicherzustellen, dass der Dämon keinen übergeordneten Prozess hat und auf init repariert wird - dies geschieht jedoch trotzdem, sobald der übergeordnete Prozess im Fall einer einzelnen Gabelung stirbt.

Ich nehme an, am Ende läuft alles nur auf Tradition hinaus - eine einzige Gabel reicht aus, solange der Elternteil ohnehin in kurzer Zeit stirbt.

bdonlan
quelle
2

Eine anständige Diskussion darüber scheint unter http://www.developerweb.net/forum/showthread.php?t=3025 zu sein

Zitiert mlampkin von dort:

... stellen Sie sich den Aufruf von setsid () als die "neue" Vorgehensweise vor (trennen Sie sich vom Terminal) und den darauf folgenden Aufruf von [second] fork () als Redundanz, um mit dem SVr4 umzugehen ...

Stobor
quelle
-1

Auf diese Weise ist es möglicherweise einfacher zu verstehen:

  • Der erste Fork und die erste Setsid erstellen eine neue Sitzung (aber die Prozess-ID == Sitzungs-ID).
  • Die zweite Verzweigung stellt sicher, dass die Prozess-ID! = Sitzungs-ID ist.
pandy.song
quelle