Evolution Programming Resources |
Unter NT stehen höchst unterschiedliche Formen der Interprozesskommunikation bereit.
Die Palette reicht von DDE über Mailslots bis Sockets. Doch auch (oder gerade) die
einfachste Form der Kommunikation - die von Unix bekannten Pipes - birgt einige
Tücken.
Pipelines existieren unter NT in zwei Formen: zum einen den anonymous Pipes, die
hauptsächlich für die Kommunikation zwischen Vater- und Kindprozess verwendet
werden und den named Pipes, die die Kommunikation unterschiedlichster Prozesse auch
über Rechnergrenzen hinweg ermöglichen.
Während bei meinen Versuchen mit den named Pipes nur selten Probleme aufgetaucht sind,
erwiesen sich die anonymous Pipes als wesentlich schwerer zu implementieren. Deshalb
beschäftigt sich dieser Artikel nur mit den anonymous Pipes.
Eine anonymous Pipe unterscheidet sich von seinem benannten Vetter nur in einer Eigenschaft:
sie hat keinen Namen. Auf sie wird rein über Handles zugegriffen. Und damit offenbart sich
schon das erste Problem: wie kommt der Clientprozess an das Handle? Man kann zwar
das Handle über einen anderen Interprozesskomunikationmechnismus austauschen (zur Not
sogar Dateien), aber im allgemeinen wird man in diesem Fall auf named Pipes zurückgreifen.
Wozu sind anonymous Pipes also nütze??
Nun, wie ich oben schon bemerkt habe verwendet man sie vor allem zur Kommunikation zwischen
verwandten Prozessen. Dabei verbindet der Vaterprozess die Pipeline mit stdout bzw/und
stdin des Kindprozesses. Der Kindprozess kann anschliessend ganz normal von stdin lesen und
auf stdout schreiben und damit die Daten der Vaterprozesses übernehmen und Ergebnisse
zurückliefern.
Eine anonymous Pipe wird durch den Befehl CreatePipe erzeugt. Es werden zwei Handles
zurückgeliefert: eines für das Schreibende und eines für das Leseende der Pipeline.
Um nun eines dieser Handles dem Clientprozess als stdin oder stdout übergeben zu können,
muß eines der Handles als "vererbbar" gekennzeichnet werden.
Vererbbar bedeutet in diesem Zusammenhang, das der Kind-Prozess auf ein vom Vaterprozess
geöffnetes Objekt Zugriff hat. Dabei werden alle Eingenschaften des Orginalobjekts ebenfalls
vererbt: der Stand der Schreib/Lesezeigers bei einer Datei, der Zustand eines Events etc.
In unserem Fall ist es wichtig, das nur das Ende einer Pipeline vererbt wird, das auch
wirklich von dem Kindprozess verwendet wird. Wird nämlich ein Handle an einen anderen
Prozess als stdin oder stdout übergeben ruft Windows NT intern DuplicateHandle auf. Für
jedes Objekt gibt es einen Zähler, der durch ein DuplicateHandle erhöht und
durch ein CloseHandle erniedrigt wird. Erst wenn der Zähler auf Null fällt, wird das
entsprechende Objekt gelöscht.
Vererben wir also beide Enden einer Pipeline, die mit stdin unseres Kindprozesse verbunden
wird, wird intern eine zweite 'virtuelle' Pipeline erzeugt. Schliessen wird dann unser
Schreibhandle wird nur dieses virtuelle Objekt zerstört, die Orginalpipeline existiert noch
und hängt praktisch mit einem Ende in der Luft. Auf diese Weise, kann der Kindprozess
nie das Ende der Transaktion erkennen, denn für ihn signalisiert eine 'zerbrochene' Pipeline
das Ende des Files (EOF).
Eine weitere unangenehme Eigenschaft der anonymous Pipe ist, das sie nur einen beschränkten
Schreib/Lesepuffer zur Verfügung stellen. Ist dieser Puffer voll blockiert ein Schreibbefehl
(WriteFile) solange, bis der Kindprozess die Daten aus dem Puffer gelesen hat. Dies ist
eigentlich auf den ersten Blick kein Problem. Problematisch wird es erst, wenn man eine
Pipeline verwendet um auf stdin der Kindprozesses zu schreiben und eine weitere um von
seinem stdout zu lesen. Dabei kommt es, verwendet man ein abwechselndes Lesen und Schreiben
(z.B. in einer Schleife) häufig (und unvorhersagbar) zu Deadlocks.
Dies geschieht einfach auf folgende Weise: der Vaterprozess schreibt eine viele Daten
(mehr als die Größe des Puffers) in die Pipeline, während der Kindprozess noch
das vorhergehende Datenpaket verarbeitet. Der Vaterprozess blockiert. Jetzt ist der
Kindprozess mit seiner Verarbeitung fertig und schreibt das Ergebnis das ebenfalls
größer ist, als der Puffer auf stdout. Jetzt blockiert auch er. Deadlock. Der Vater
wartet darauf, das der Sohn die Daten abholt und umgekehrt.
Deshalb es es nötig, im Vaterprozess das Lesen von stdout und das Scheiben auf stdin
als eigenen Thread zu implementieren.
Doch jetzt genug der Theorie. Auf in die Praxis. Das folgende kurze Beispielprogramm zeigt, wie man unter Windows NT den Kommandointerpreter cmd im Hintergrund started, ein kurzes Batchfile ablaufen läßt und die Daten vom Kommandointerpreter übernimmt.
|