Stefan Reuther <
stefa...@arcor.de> writes:
> Rainer Weikusat wrote:
>> Stefan Reuther <
stefa...@arcor.de> writes:
> [fork vs. multithreaded]
>>>Und auch aktuelle Versionen von POSIX bzw. SUS erlauben zwischen fork()
>>>und exec() nur "async signal safe" Operationen. Dinge wie malloc/free,
>>>die in einem C++-Programm gerne mal implizit benutzt werden, sind da
>>>nicht darunter.
>>
>> Das ist zum einen nicht richtig:
>>
>> A process shall be created with a single thread. If a
>> multi-threaded process calls fork(), the new process shall
>> contain a replica of the calling thread and its entire address
>> space, possibly including the states of mutexes and other
>> resources. Consequently, to avoid errors, the child process
>> may only execute async-signal-safe operations until such time
>> as one of the exec functions is called.
>
> ��h, da steht genau das, was ich schrieb. Der Prozess darf nur "async
> signal safe" Operationen ausf�hren. Ja, da steht "to avoid errors",
> nicht "otherwise, the behaviour is undefined", aber das kommt aufs
> gleiche raus.
Zunaechst mal steht da 'if a multi-threaded process calls fork'. Nicht
jeder Prozess ist multi-threaded und die 'universelle Annahme' das zu
jedem Prozess irgendwelcher Code gehoert, von dem niemand weiss, was
er eigentlich genau tut, ist falsch: Das ist bloss der allgemeinste
denkbaren Fall, den diese Spezifiaktion abdecken muss.
Technisch gesehen sieht das so aus, das 'fork' sich zu anderen threads
genauso verhaelt, wie ein asynchron ausgefuehrter signal handler:
Diese werden zu einem beliebigen Zeitpunkt ohne Warnung und ohne die
Noeglichkeit, auf dieses Ereignis reagieren zu koennen,
unterbrochen. Je nachdem, was diese 'anderen threads' gerade am tun
waren, koennten sich also 'irgendwelche Datenstrukturen' im neuen
Prozess in einem 'inkorrekten' Zustand befinden und 'irgendwelcher
Code', der auf Annahmen ueber diese Datenstrukturen basiert, die in
Abwesenheit einer solchen 'asynchronen Unterbrechung' garantiert sind,
koennte deswegen 'unerwartetes Verhalten' an den Tag legen.
Etwas kuerzer koennte man diese Konjunktiv-Lawine als "Kraeht der Hahn
auf dem Mist, dann aendert sich's Wetter oder's bleibt wie's ist"
ausdruecken: Irgendetwas wird passieren. Leider haben wir keine Idee,
was genau, aber die Folgen werden furchtbar sein! Das ist kein
nuetzlicher Standpunkt fuer die Loesung technischer Probleme.
>>>Und das ist genau das Problem: fork() kann eben Mutexe nicht auf eine
>>>Weise klonen, dass sie nach dem fork() im Kindprozess noch brauchbar
>>>sind:
>>
>> Fork kann das ganz wunderbar. 'Das Problem' ist hier das Verhalten,
>> dass die UNIX(*)-Norm fuer 'Mutex-Objekte' fordert, naemlich dass
>> diese nur von dem thread, dem sie gehoeren, wieder freigegeben werden
>> koennen. Wenigstens fuer Linux wird das von der C-Library dadurch
>> sichergestellt, dass die Bibliotheksroutine eine Fehler zurueckgibt,
>> anstatt den Zustand des Objektes zu aendern, falls die gespeichter
>> Thread-Id nicht mit der aktuellen Thread-ID uebereinstimmt.
>
> Richtig. Deswegen schrieb ich "noch brauchbar sind". Irgendwas irgendwie
> klonen wird schon gehen. Nur kann der Kernel hier nur verlieren:
> entweder er klont den Mutex im gesperrten Zustand, dann kann ihn niemand
> jemals wieder freigeben. Oder er entsperrt ihn im Kindprozess, dann ist
> aber die Datenstruktur, die er sch�tzen sollte, immer noch
> inkonsistent.
Der Kernel weiss davon gar nichts (oder sollte zumindestens nichts
davon wissen): Die pthread-Sychronisationsobjekte sind samt und
sonders dafuer gedacht, aus Performance-Gruenden soweit als moeglich
ohne Kernelbenutzung auszukommen. Andernfalls koennte man AF_UNIX
'datagram sockets' benutzen (damit kann Semaphore implementieren und
somit 'auch alles andere'). Das sind Probleme mancher Anwendungen, um
die sich diese Anwendungen selber kuemmern muessen oder muessten.
ZB kann ein geforkter Prozess, dessen Vaterprozess (klingt nicht
wirklich gut) zu einem Zeitpunkt fork aufgerufen hat, an dem alle
anderen threads entweder 'lockfrei' blockiert waren oder bei
Operationen unterbrochen wurden, die 'async signal safe' waren, alles
tun, was er moechte, ohne 'seltsame Effekte' zu erwarten zu
haben. Alternativ kann der thread, der fork aufruft, vorher alle
Mutexe sperren und nach dem fork wieder entsperren. Beides habe ich
schon benutzt. Ggf kuemmert sich auch die pthread-Implementierung gar
nicht um diese willkuerliche Einschraenkung (aka 'LinuxThreads') oder
es werden lock-Objekte benutzt, fuer die sie nicht gilt
(POSIX-Semaphore anstelle von Mutexen).
[...]
>> Die Loesung fuer diese Problem ist dafuer zu sorgen, dass sich alle
>> relevanten Bibliotheksobjekte in einem 'geeigneten' Zustand befinden,
>> bevor ein Fork-Aufruf erfolgt.
>
> Nicht nur die Bibliotheksobjekte, *alle* Objekte. OK, es gibt
> pthread_atfork, das Problem ist prinzipiell l�sbar. Aber Spa� ist was
> anderes.
pthread_atfork ist IIRC (zZt laeuft hier kein X, 'einfaches Suchen'
ist also nicht moeglich) selber nicht 'async signal safe' und somit
zur Loesung dieser Probleme aufgrund von historischen Defiziten in der
Spezifiaktion nicht geeignet.
>>>Der Kindprozess k�nnte also, hinreichend br�sige Bibliothek voraus-
>>>gesetzt, an der "}" nach dem fork() h�ngen bleiben beim Wegr�umen
>>>irgendeines C++-Objekts.
>>
>> 'Hinreichend braesige Bibliotheken vorrrausgesetzt' koennten 'beim
>> Wegraeumen eines C++-Objektes' auch die ersten zwanzig Code-Bytes
>> einer zufaellig ausgewaehlten Unteroutine mit Zufallszahlen
>> ueberschrieben werden. Das sind dann konkrete technische Probleme,
>> gegen die man konkret etwas tun kann (zB koennte man annehmen, dass
>> 'C++Bibliothek' und 'hinreichende braesig' synonym sind und den Code
>> in einem Quarantaene-Prozess isolieren, der mit dem eigentlichen
>> Programm nur via IPC interagiert :-).
>
> Der Quarant�ne-Prozess zum Abforken von Kindprozessen ist im allgemeinen
> eine gute Idee, wenn man multithreaded arbeitet.
Ich hatte allerdings schon einen (oder mehrere) Spezialprozesse zum
isolieren von 'flusigem Code'[*] gemeint: Was ich nicht ohne groessere
Schwierigkeiten aendern kann oder darf, fummelt mir nicht in MEINEM
Adressraum rum (soweit moeglich). Das bekommt seinen eigenen, wo es im
Zweifels mit einem Knueppel erschlagen (SIGKILL) und neugestartet
werden kann, ohne die 'Kernlogik' der Anwendung
durcheinanderzubringen. Damit kann man sich einen Haufen graue Haare
ersparen.
[*] ZB Bibliotheken, deren mitgelieferte Dokumentation sich
offensichtlich auf eine vollkommen andere Bibliothek mit aehnlicher
generaller Zielsetzung bezieht.