Pipes: Rohrpost zwischen Prozessen

Hätte man “Pipes” in der Programmierung als “Pipeline” bezeichnet, würden vielleicht mehrere Entwickler den Namen kennen und die Technik einsetzen.

Linux bzw. Unix - Fans wissen mit dem Begriff gut umzugehen, doch auf Seiten der Windows Gemeinde fehlt dieses Wissen leider nur all zu oft.

Dabei sind Pipes eine schöne klassische Form Daten zwischen Eltern- und Kindprozessen auszutauschen.

Pipes sind virtuelle Datenschläuche. Was man auf den einen (Schreib-)Seite hineinfallen lässt, kommt auf der anderen (Lese-)Seite wieder heraus. Wird eine Pipe erzeugt, erhalten wir in der Regel zwei Handles bzw. Filedescriptoren, nämlich ein Element zum Hineinschreiben und eines zum Herauslesen.

Wenn man von einem kleinen Puffer mal absieht, blockiert ein Schreibvorgang so lange, bis die Leseseite die Daten gelesen hat. Umgekeht blockiert ein Lesevorgang so lange, bis mindestens ein Byte auf der anderen Seite geschrieben wurde.

Anonymous vs. Named Pipes

Zu den ältesten und bekanntesten anonymen Pipe-Formen zählen STDIN, STDOUT und STDERR, also die Standard-Ein- und -Ausgabe eines Prozesses. Sie werden bei der Erzeugung eines Prozesses vom Elternprozess entweder explizit vorgegeben oder von diesem einfach geerbt.

Viele Pipes sind anonym, also namenlos. Bei ihrer Erzeugung entstehen die beiden erwähnten Lese- und Schreib-Handles/Descriptoren, und die Pipe existiert so lange, wie die Handles darauf offen sind.
Ein Prozess kann ein solches Handle zwar klonen, aber von außen kommt man nicht (bzw. nur sehr schwer) an sie heran.

Benannte Pipes (Named Pipes) erhalten einen eindeutigen Namen und können explizit von jedem Prozess geöffnet werden, der die nötigen Rechte hat. Damit lässt sich quasi eine kleine lokale Server-Client-Architektur aufsetzen um die Pipe wie einen Server oder Client zu nutzen.

Windows

Windows stellt mit den beiden APIs CreatePipe() und CreateNamedPipe() alles bereit, was man für die Erzeugung beider Formen benötigt. Zum Öffnen einer bestehenden Named-Pipe dient CreateFile() und gelesen bzw. geschrieben wird die Pipe ganz klassisch mit ReadFile() und WriteFile().

CreatePipe() hat leider den Nachteil, dass man damit keine asynchronen IO-Funktionen (Stichwort: Overlapped) damit nutzen kann.
Die GATE Implementierung nutzt daher den Umweg, dass eine Named-Pipe mit einem zufälligen Namen erzeugt wird, denn hier kann man Overlapped-IO einsetzen.

Windows CE

Windows CE kennt - so weit ich weiß und zu meiner Verwunderung - keine Pipes außer Konsolen (STDIN, STDOUT) auf einigen Plattformen (aber nicht auf allen).

Es gibt eine Ersatztechnologie mit dem Namen “Message-Queues”. Diese können ähnlich wie Pipes genutzt werden, haben aber andere APIs und sind auch keine echten Pipes.

In der früheren GATE-Implementierung hatte ich dieses Verfahren mal ausprobiert, aber nie einen praktischen Nutzen daraus gezogen. Falls ich mal wieder dazu kommen sollte, gibt es sich einen Beitrag dazu.

POSIX, BSD, Linux

Hier wurden Pipes geboren, denn unter Unix Systemen ist es Gang und Gäbe, dass Programme Pipes erzeugen, sie an ihre Kindprozesse weitervererben und so mit ihrem “Nachwuchs” kommunizieren.

pipe() erzeugt ein Filedescriptor-Paar zum anonymen Lesen und Schreiben, und die üblichen Verdächtigen read() und write() sorgen für den Zu- bzw. Abfluss von Bytes.

Benannte Pipes werden als “besondere” Dateien im Dateisystem abgelegt. Obwohl es keine zwingende Vorgabe gibt, wo das sein soll, empfiehlt sich ein standardisierter Ort, wie /var oder /tmp)

mkfifo() erzeugt diese spezielle FIFO-Pipe-Datei, die mit open() entweder zum Lesen oder zum Schreiben separat geöffnet werden kann.

Im Gegensatz zu Windows bleibt eine solche FIFO-Datei auch erhalten, wenn ihr Erstellerprozess und alle anderen Nutzer beendet wurden. Man kann bzw muss sie dann per unlink() manuell löschen.

Dank der Tatsache, dass alle Deskriptoren per select() ansteuerbar sind, lassen sich alle Pipes problemlos mit asynchroner Kommunikation einsetzen.

Fazit

Nutzt mehr PIPES, liebe Leute.
Wie oft musste ich schon erleben, dass zum Austausch von 5 Einstellungs-Strings eine Datenbank zwischen Prozessen einprogrammiert wurde, anstatt diese Infos einfach per Rohrpost zum Nachbarn zu schicken.

Man braucht keine separaten Server für solche Aufgaben, die Megabytes an RAM fressen und sau-langsam sind.
Pipes schaffen das mit wenigen Kilobytes mit annähernd Lichtgeschwindigkeit!


Wenn sich eine triviale Erkenntnis mit Dummheit in der Interpretation paart, dann gibt es in der Regel Kollateralschäden in der Anwendung.
frei zitiert nach A. Van der Bellen
... also dann paaren wir mal eine komplexe Erkenntnis mit Klugheit in der Interpretation!