Warum sollte fork () so konzipiert sein, dass es einen Dateideskriptor zurückgibt?

15

Dan Bernstein erklärt auf seiner Webseite über den Self-Pipe-Trick einen Race-Condition mit select()und signalisiert, bietet einen Workaround an und schließt daraus

Das Richtige wäre natürlich, fork()einen Dateideskriptor zurückzugeben, keine Prozess-ID.

Was meint er damit select()? Geht es darum, untergeordneten Prozessen die Möglichkeit zu geben , ihre Statusänderungen zu verarbeiten, anstatt einen Signalhandler zu verwenden, um über diese Statusänderungen informiert zu werden?

Lassi
quelle
Verwechselt dieser Artikel Eingabe und Ausgabe, oder lese ich ihn nicht richtig?
Strg-Alt-Delor
Sie können die Lieferung von Signalen über Leitungen anfordern. Das ist was ich mache.
Strg-Alt-Delor
@ ctrl-alt-delor, ja, er scheint seltsamerweise "Pipe Input / Output" zu verwenden, aber ich denke, es ist klar, wo er schreibt und wo er aus einer Pipe liest. Dieser Text stammt aus dem Jahr 2003, und ich bin mir nicht sicher, ob es signalfdso etwas gab?
Ilkkachu
5
Dan weiß, wovon er spricht, obwohl er absichtlich etwas provokativ sein kann. Wenn ich absichtlich provokativ wäre, würde ich das sagen. Natürlich wäre es das Richtige, SIGCHLD loszuwerden.
Steve Summit
1
@mosvy Ich übertreibe ein bisschen, aber jedes Programm und jeder Programmierer, den ich je gesehen habe und der versucht hat, SIGCHLD zu verwenden, hatte Probleme damit. Es ist eine Rennbedingung, die darauf wartet, passiert zu werden. Damals, als wir nur blockiert hatten wait(), gab es Dinge, die Sie nicht tun konnten, also hat jemand SIGCHLD erfunden, aber es war ein schlechter Job. Nach meiner Erfahrung, und jetzt, wo sie existieren, Beregnung schön, nicht blockierend wait3(), wait4()und / oder waitpid()Anrufe an wichtigen Stellen (vielleicht Ihre Hauptereignisschleife) ist eine weit überlegene Alternative.
Steve Summit

Antworten:

14

Das Problem ist dort in deiner Quelle beschrieben, select()sollte durch Signale wie unterbrochen werden SIGCHLD, aber in manchen Fällen klappt es nicht so gut. Die Problemumgehung besteht also darin, ein Signal in eine Pipe zu schreiben, die dann von überwacht wird select(). Das Ansehen von Dateideskriptoren ist das Richtige select(), damit das Problem umgangen wird.

Die Problemumgehung verwandelt das Signalereignis im Wesentlichen in ein Dateideskriptorereignis. Wenn Sie fork()nur ein fd zurückgeben, ist die Problemumgehung nicht erforderlich, da dieses fd dann vermutlich direkt mit verwendet werden könnte select().

Also ja, Ihre Beschreibung im letzten Absatz scheint mir richtig zu sein.


Ein weiterer Grund dafür, dass ein fd (oder eine andere Art von Kernel-Handle) besser ist als eine einfache Prozess-ID-Nummer, besteht darin, dass PIDs nach dem Abbruch des Prozesses wiederverwendet werden können. Dies kann in einigen Fällen ein Problem sein, wenn Signale an Prozesse gesendet werden. Es ist möglicherweise nicht sicher, ob der Prozess der Prozess ist, von dem Sie glauben, dass er es ist, und nicht ein anderer, der dieselbe PID wiederverwendet. (Obwohl ich denke, dass dies kein Problem sein sollte, wenn Signale an einen untergeordneten Prozess gesendet werden, da der übergeordnete Prozess auf dem untergeordneten Prozess ausgeführt werden muss, wait()damit seine PID freigegeben wird.)

ilkkachu
quelle
Trotzdem kann ich mich nicht genau an die Fälle erinnern, in denen ich gelesen habe, dass die Wiederverwendung von PIDs ein Problem darstellt. Wenn jemand dies näher erläutern oder sogar die obigen Änderungen vornehmen möchte , kann er dies gerne tun.
Ilkkachu
2
Wie Sie bereits sagten, gibt es keine Entschuldigung dafür, dass ein Elternteil feststellt, dass seine eigene Kinder-PID wiederverwendet wurde. Es hat die volle Kontrolle über diese Situation, weil es derjenige ist, der anruft wait().
Joshua
Diese Prozesse werden als Zombie-Prozesse bezeichnet : "Ein Prozess, der die Ausführung abgeschlossen hat, aber noch einen Eintrag in der Prozesstabelle hat: Es handelt sich um einen Prozess im Status" Abgebrochen ". Dies tritt bei untergeordneten Prozessen auf, bei denen der Eintrag noch erforderlich ist, um den übergeordneten Prozess zuzulassen Prozess, um den Austrittsstatus seines Kindes zu lesen "
Lassi
6
Es ist erwähnenswert , dass jetzt Linux kann eine Dateibeschreibung zurückgeben (pidfd) aus clone, das die tatsächliche Systemaufruf ist , dass Gabel Invokes auf LInux. Das Flag, um dies zu aktivieren, heißt CLONE_PIDFD- siehe zum Beispiel lwn.net/Articles/784831 .
KJ Tsanaktsidis
1
@Lie Ryan, Re "Das Zurückgeben eines globalen statt eines lokalen Handles durch fork () ist konzeptionell korrekter, weil " Windows Prozesshandles verwendet. Der PID- und Exit-Code bleibt erhalten, bis alle Punkte des Prozesses geschlossen sind (anstatt auf das Ernten des übergeordneten Elements zu warten), wodurch die in Unix-Systemen üblichen Race-Bedingungen vermieden werden. Wenn die Handles den Prozess am Leben erhalten, ist es weitaus sinnvoller, dass es sich um lokale und nicht um globale Handles handelt.
Ikegami
9

Es ist nur eine Überlegung im Sinne von "Es wäre großartig, wenn Unix anders gestaltet wäre als es ist".

Das Problem mit PIDs ist, dass sie in einem globalen Namespace leben, in dem sie für einen anderen Prozess wiederverwendet werden können. Es wäre schön, wenn fork()im übergeordneten Element eine Art Handle zurückgegeben würde, das garantiert immer auf den untergeordneten Prozess verweist könnte über Vererbung oder Unix-Sockets / SCM_RIGHTS[1] an andere Prozesse übergeben werden .

Siehe auch die Diskussion hier für einen kürzlichen Versuch, dies unter Linux zu "beheben", einschließlich des Hinzufügens eines Flags, durch clone()das ein pid-fd anstelle einer PID zurückgegeben wird.

Aber selbst dann würde das die Notwendigkeit dieses Self-Pipe-Hacks [2] oder besserer Schnittstellen nicht beseitigen, da die Signale, die einen übergeordneten Prozess über den Zustand eines Kindes benachrichtigen, nicht die einzigen sind, die Sie in der Hauptschleife behandeln möchten des Programms. Leider sind Dinge wie epoll(7) + signalfd(2)Linux oder kqueue(2)BSD nicht Standard - die einzige Standardschnittstelle (die auf älteren Systemen nicht unterstützt wird) ist die viel schlechtere pselect(2).

[1] Es waitpid()könnte wahrscheinlich auf neueren Systemen durch Verwendung von erreicht werden, dass verhindert wird, dass die PID bis zur Rückkehr des Systemaufrufs und der Verwendung des Rückgabewerts erneut durchlaufen wird waitid(.., WNOWAIT).

[2] Ich würde DJ Bernstein nicht darauf hinweisen, dass er es erfunden hat (sorry für die Entschuldigung ;-)).

Mosvy
quelle
8

Bernstein gibt nicht viel Kontext für diese "Richtige" Bemerkung, aber ich werde eine Vermutung wagen: Wenn fork (2) eine PID zurückgibt, ist dies inkonsistent mit open (2), creat (2) usw., die Dateideskriptoren zurückgeben. Der Rest des Unix-Systems hätte die Prozessmanipulation mit einem Dateideskriptor durchführen können, der einen Prozess anstelle einer PID darstellt. Es gibt einen Systemaufruf signalfd (2) , der eine etwas bessere Interaktion zwischen Signalen und Dateideskriptoren ermöglicht und zeigt, dass ein Dateideskriptor, der einen Prozess darstellt, funktionieren könnte.

Bruce Ediger
quelle
signalfd (2) sieht fantastisch aus, danke, dass du es erwähnt hast! Schade, dass es nur Linux ist.
Lassi
1
Es gab Diskussionen auch über pidfd_openLinux, siehe zum Beispiel lwn.net/Articles/789023
dhag