Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger

 << zurück
Linux-UNIX-Programmierung von Jürgen Wolf
Das umfassende Handbuch – 2., aktualisierte und erweiterte Auflage 2006
Buch: Linux-UNIX-Programmierung

Linux-UNIX-Programmierung
1216 S., mit CD, 49,90 Euro
Rheinwerk Computing
ISBN 3-89842-749-8
gp Kapitel 9 IPC – Interprozesskommunikation
  gp 9.1 Unterschiedliche Interprozesskommunikations-Techniken im Überblick
    gp 9.1.1 (Namenlose) Pipes
    gp 9.1.2 Benannte Pipes (FIFO-Pipes)
    gp 9.1.3 Message Queue (Nachrichtenspeicher)
    gp 9.1.4 Semaphore
    gp 9.1.5 Shared Memory (gemeinsamer Speicher)
    gp 9.1.6 STREAMS
    gp 9.1.7 Sockets
    gp 9.1.8 Lock Files (Sperrdateien)
    gp 9.1.9 Dateisperren (Record Locking)
  gp 9.2 Gründe für IPC
  gp 9.3 Pipes
    gp 9.3.1 Eigenschaften von Pipes
    gp 9.3.2 Pipes einrichten – pipe()
    gp 9.3.3 Eigenschaften von elementaren E/A-Funktionen bei Pipes
    gp 9.3.4 Standard-E/A-Funktionen mit pipe
    gp 9.3.5 Pipes in einen anderen Prozess umleiten
    gp 9.3.6 Filterprogramm erstellen mithilfe einer Pipe
    gp 9.3.7 Einrichten einer Pipe zu einem anderen Prozess – popen()
    gp 9.3.8 Mail versenden mit Pipes und Sendmail
    gp 9.3.9 Drucken über eine Pipe mit lpr
    gp 9.3.10 Benannte Pipes – FIFOs
  gp 9.4 System-V-Interprozesskommunikation
    gp 9.4.1 Gemeinsamkeiten der SysV-Mechanismen
    gp 9.4.2 Ein Objekt einrichten, eine Verbindung herstellen und das Objekt wieder löschen
    gp 9.4.3 Datenaustausch zwischen nicht verwandten Prozessen
  gp 9.5 Semaphore
    gp 9.5.1 Lebenszyklus eines Semaphors
    gp 9.5.2 Ein Semaphor öffnen oder erstellen – semget()
    gp 9.5.3 Abfragen, Ändern oder Löschen der Semaphormenge – semctl()
    gp 9.5.4 Operationen auf Semaphormengen – semop()
    gp 9.5.5 Semaphore im Vergleich mit Sperren
  gp 9.6 Message Queues
    gp 9.6.1 Eine Message Queue öffnen oder erzeugen – msgget()
    gp 9.6.2 Nachrichten versenden – msgsnd()
    gp 9.6.3 Eine Nachricht empfangen – msgrcv()
    gp 9.6.4 Abfragen, Ändern oder Löschen einer Message Queue – msgctl()
  gp 9.7 Shared Memory
    gp 9.7.1 Ein Shared-Memory-Segment erstellen oder öffnen – shmget()
    gp 9.7.2 Ein Shared-Memory-Segment abfragen, ändern oder löschen – shmctl()
    gp 9.7.3 Ein Shared-Memory-Segment anbinden (attach) – shmat()
    gp 9.7.4 Ein Shared-Memory-Segment loslösen – shmdt()
    gp 9.7.5 Client-Server-Beispiel – Shared Memory

Kapitel 9 IPC – Interprozesskommunikation

Wenn Prozesse untereinander kommunizieren, aufeinander warten oder Daten und Ressourcen austauschen müssen, werden so genannte Interprozesskommunikationen verwendet.

Dass Linux ein Multitasking-System ist, dürften/sollten Sie (spätestens seit dem Kapitel über Prozesse) bereits erfahren und verstanden haben. Dazu gehört auch, dass die meisten der vielen Prozesse, die nebeneinander herlaufen, so gut wie nichts voneinander darüber wissen, welche Daten diese z. B. enthalten. Realisiert wird das Ganze durch einen Speicherschutz. Dieser dient zum Schutz des Betriebssystems, denn würde ein Prozess auf die Speichersegmente eines anderen Prozesses zugreifen, so beeinträchtigt dies nur die Stabilität der Programme (Absturz/Programmierfehler) oder gar des Gesamtsystems. Voraussetzung (hardwaremäßig) ist dafür eine Speicherverwaltungseinheit (MMU). In der Regel ist diese Voraussetzung bis auf wenige Ausnahmen auf allen Betriebssystemen gegeben. Ausnahmen stellen meistens so genannte embedded Systeme dar, auf denen häufig nur einzelne Anwendungen laufen.


Hinweis   Dieser Speicherschutz wird allerdings (besonders bei x86-Prozessoren) immer häufiger unterlaufen. Einige Viren nutzen z. B. den Fehler einiger Anwendungen bzw. des Betriebssystems aus, um einen Buffer Overflow zu erzeugen, um den Speicherschutz (schlimmstenfalls) ganz außer Kraft zu setzen.


Dennoch gibt es Anwendungsfälle, wo man sich gerne einen »Tunnel« zu einem anderen Prozess graben muss. Einige Gründe hierfür wären:

gp  Mehrere Prozesse müssen spezielle Daten gemeinsam verwenden.
gp  Die Prozesse sind untereinander abhängig und müssen aufeinander warten.
gp  Daten müssen von einem Prozess zu einem anderen weitergereicht werden.
gp  Die Verwendung von Systemressourcen muss koordiniert werden.

Für solche Anwendungsfälle wurden bereits im System V (SysV) schon vor längerer Zeit zuverlässige und bewährte Techniken entwickelt, die heute in vielen UNIXen (u. a. auch Linux) implementiert sind. Diese Mechanismen werden unter dem Begriff »Interprozesskommunikation« (kurz IPC) zusammengefasst. Da auch der Betriebssystemkern selbst (vor allem auf Mehrprozessorsystemen) mit Problemen, die eben aufgezählt wurden, zu kämpfen hat, wurden einige dieser Mechanismen in den Kernel implementiert.


Rheinwerk Computing

9.1 Unterschiedliche Interprozesskommunikations-Techniken im Überblick  downtop

Eine Technik, wenn auch sehr beschränkt, haben Sie bereits ein Kapitel zuvor anhand der Signale kennen gelernt. Im folgenden Abschnitt sollen die wichtigsten Interprozesskommunikations-Mechanismen erläutert werden. Viele dieser Techniken werden Sie im Verlaufe des Kapitels bzw. des Buchs noch näher kennen lernen.


Rheinwerk Computing

9.1.1 (Namenlose) Pipes  downtop

Pipes, wie auch FIFOs (named Pipes), sind die einzigen beiden IPCs, die garantiert auf jedem System vorhanden sind. Sie sind sowohl POSIX-, SVR4-, XPG3- als auch BSD-konform.

Eine Pipe ist ein unidirektionaler Kommunikationskanal zwischen zwei verwandten Prozessen (sie kann auch innerhalb eines einzigen Prozesses genutzt werden – inwiefern das sinnvoll ist, ist eine andere Frage). Sie haben Pipes sicherlich schon des Öfteren in der Konsole verwendet. Beispielsweise:

$ ps ax | less

Hiermit haben Sie in der Shell zwei Prozesse gestartet. Ein Prozess führt das Kommando ps ax aus und schreibt das Ergebnis gewöhnlich an die Standardausgabe. Durch die Pipe (|) wird allerdings diese Standardausgabe an den Prozess less weitergeleitet. less liest hierbei die Daten von der Standardeingabe ein und gibt aus, was ihm das Kommando ps durch die Pipe so schickt. Natürlich erfolgt die Ausgabe mit dem gewöhnlichen Komfort, den less ihnen auch sonst bietet.

Wenn die Daten in beide Richtungen ausgetauscht werden sollen, muss eine zweite Pipe dazu verwendet werden. Sie dürfen sich eine Pipe gerne wie ein Rohr vorstellen, wo die Daten in die eine Seite (Prozess A) hineingesteckt werden und bei Prozess B wieder herauskommen.

Eine Pipe dient außer zur Kommunikation zwischen zwei Prozessen hervorragend zur Flusskontrolle. Dies daher, weil eine Pipe nur eine bestimmte Menge an Daten aufnehmen kann (normalerweise 4 KB, 8 KB oder AIX 32 KB; siehe Konstante PIPE_BUF in limits.h oder auch ulimit -p). Ist die Pipe (bzw. deren Puffer) voll, wird ein Prozess mindestens so lange angehalten, bis mindestens ein Byte aus der vollen Pipe gelesen wurde und wieder Platz vorhanden ist, um die Pipe wieder mit Daten zu befüllen. Andersherum dasselbe Bild, ist die Pipe leer, wird der lesende Prozess so lange angehalten, bis der schreibende Prozess etwas in diese Pipe schickt.

Es gibt also eine Schreibseite und eine Leseseite bei einer Pipe. Somit ist also nur eine Kommunikation in einer Richtung möglich (half-duplex). Sie können sich das Beispiel oben so vorstellen:


Abbildung
Hier klicken, um das Bild zu vergrößern

Abbildung 9.1    Kommunikation zwischen zwei Prozessen über eine Pipe



Rheinwerk Computing

9.1.2 Benannte Pipes (FIFO-Pipes)  downtop

Mit den Pipes können Sie allerdings nur mit den Prozessen kommunizieren, die miteinander verwandt sind. Also nur zwischen geforkten Prozessen. Mit FIFOs (benannten Pipes) haben Sie nun die Möglichkeit, mit einem völlig fremden Prozess zu kommunizieren (Daten austauschen), da solche Pipes über einen Dateinamen angesprochen werden können.


Hinweis   Ich verwende hier vorwiegend den Begriff FIFO statt »benannte Pipe«, da hierbei gerne ein Durcheinander im Gehirn mit der namenlosen Pipe entsteht.


Intern (wenn man den Kernel betrachtet) ist ein FIFO (FIFO = First In First Out) tatsächlich nichts anderes als die Implementierung einer namenlosen Pipe. Der Systemaufruf mkfifo() bedeutet also nichts anderes, als dass eine Pipe als Filesystem-Objekt repräsentiert wird. Wer mittels pipe() eine Pipe erstellt, kreiert auch eine Datei wie mit mkfifo()! Diese sieht man zwar nicht, weil sie in einem Dateisystem versteckt ist, das man nicht mounten kann – aber wer in /proc/1234567/fd nachgesehen hat, sieht bei einigen »pipe:[1692]« mit pipe()-generierten Pipes. Ähnliches gilt übrigens auch für Sockets (TCP, auch PF_UNIX) (»socket:[456789]«). FIFOs werden wie erwartet mit z. B. /dev/initctl angezeigt (siehe /proc/1/fd/). PF_UNIX-Sockets werden ebenfalls als socket:[]-Objekt dargestellt, auch wenn diese eigentlich einen Dateinamen besitzen.

Auf der Shell lässt sich ein FIFO folgendermaßen erstellen:

$ mkfifo fifo1

Bei einem Blick ins aktuelle Arbeitsverzeichnis finden Sie das FIFO unter folgendem Eintrag:

$ ls -l
prw-r--r--    1 tot      users      0 2003–12–07 10:53 fifo1 

Am p am Anfang erkennen Sie das FIFO. Sie könnten jetzt etwas in das FIFO schreiben:

[tty1]
$ echo Der erste Eintrag in das FIFO > fifo1
[tty2]
$ echo Der zweite Eintrag in das FIFO > fifo1 

Beide Dialogstationen blockieren im Augenblick und warten, bis die Daten im FIFO ausgelesen werden. Wir öffnen eine dritte Konsole und lesen ihn aus:

[tty3]
$ cat fifo1
Der zweite Eintrag in das FIFO
Der erste Eintrag in das FIFO 

Natürlich müssen Sie auch die Zugriffsrechte für das FIFO vergeben, wer in dieses FIFO etwas schreiben und wer aus ihm lesen darf. FIFOs sind auch eine halbduplexe IPC, was bedeutet, dass auch bei FIFOs, wie schon bei namenlosen Pipes, kein mehrfaches Auslesen möglich ist. Benannte Pipes werden daher auch nur dort eingesetzt, wo eine »Einbahnstraße« gewünscht ist.


Abbildung
Hier klicken, um das Bild zu vergrößern

Abbildung 9.2    Kommunikationsmodell eines FIFO (First In First Out)



Rheinwerk Computing

9.1.3 Message Queue (Nachrichtenspeicher)  downtop

Mit diesem Mechanismus können Sie zwischen mehreren Prozessen Nachrichten austauschen. Die Nachrichten werden dabei von einem Prozess an einen Speicher (der Message Queue = Nachrichtenschlange) geschickt und können dort von einem anderen Prozess abgeholt werden. Die Größe und Anzahl der Nachrichten werden vom System festgelegt. Die Nachricht selbst besteht aus einem Text und einem Nachrichtentyp – wobei es für den Nachrichtentyp keine festen Regeln gibt, so dass es dem Programmierer überlassen bleibt, welche Funktion dem Nachrichtentyp zugeordnet werden soll. Die Nachrichten werden in der Reihenfolge, in der diese eintreffen, auch wieder ausgelesen. Fordert ein Prozess eine Nachricht an, so kann über eine Option angegeben werden, dass dieser entweder so lange angehalten wird, bis eine Nachricht eingeht, oder sofort zur Programmausführung zurückkehrt und eventuell einen Fehlercode ausgibt.


Abbildung
Hier klicken, um das Bild zu vergrößern

Abbildung 9.3    Prinzip der Message Queue



Rheinwerk Computing

9.1.4 Semaphore  downtop

Semaphore sind Zugriffsvariablen, auf die nur mit bestimmten Funktionen zugegriffen werden kann. Ein Semaphor kann mehrere Werte annehmen und ist nicht von binärer Natur. Mit den Prozessen können Sie ein Semaphor abfragen und überprüfen, ob dieses einen bestimmten Wert hat. Es ist auch möglich, dass mit einer Funktion auf mehrere Semaphore gleichzeitig zugegriffen wird. Gewöhnlich verwendet man Semaphore zur Synchronisation beim Zugriff auf kritische Betriebsmittel/Datenstrukturen oder einen kritischen Codeausschnitt. Wenn z. B. der gemeinsame Speicher (Shared Memory) von zwei oder mehreren Prozessen gleichzeitig verwendet wird, muss verhindert werden, dass diese gleichzeitig schreiben oder dass ein Prozess liest, während ein anderer zur gleichen Zeit schreibt.

Das Prinzip ist recht einfach. Will man mit einem Semaphor einen bestimmten Abschnitt schützen, wird ein vorhandener Zähler getestet, ob der Wert größer als 0 ist, und anschließend dekrementiert. Ist der Wert nicht größer als 0, wird der Prozess in einen Wartezustand versetzt, da sich scheinbar ein anderer Prozess gerade in diesem kritischen Codeausschnitt befindet. Wenn ein Prozess einen kritischen Codebereich verlassen will, muss dieser wiederum den Zähler inkrementieren, damit der kritische Bereich für andere Prozesse wieder zur Verfügung steht.


Rheinwerk Computing

9.1.5 Shared Memory (gemeinsamer Speicher)  downtop

Mit dem Shared-Memory-Mechanismus können Sie mit mehreren Prozessen auf einen gemeinsamen Datenspeicherbereich zugreifen. Um dies zu realisieren, muss zuerst ein Prozess diesen gemeinsamen Datenspeicher anlegen. Anschließend müssen alle anderen Prozesse, die ebenfalls darauf zugreifen sollen, mit diesem Datenspeicher bekannt gemacht werden. Dies geschieht, indem der Speicherbereich im Adressraum der entsprechenden Prozesse eingefügt wird. Ebenfalls muss hierbei den Prozessen mitgeteilt werden, wie diese auf den Speicherbereich zugreifen können (lesend/schreibend). Wurde all dies erledigt, kann der Datenspeicherbereich wie ein gewöhnlicher Speicherbereich verwendet werden. Da die Struktur der Speicherverwaltungseinheit von der Kernelkonfiguration abhängig ist, so dass die Größe und Aufteilung variieren kann, sollte man vorsichtig sein, wenn man einen allzu großen gemeinsamen Speicher verwendet.

Leider wurde mit dem Shared Memory IPC keine explizite Synchronisation zur Verfügung gestellt, weshalb man diese Kontrolle selbst noch mit z. B. Sperren oder Semaphoren herstellen muss.


Rheinwerk Computing

9.1.6 STREAMS  downtop

STREAMS (nicht zu verwechseln mit den Streams der Standard-E/A-Funktionen) wurden ursprünglich unter UNIX als Teil von als Network Support Services bezeichneten Funktionen eingeführt. Da sich STREAMS aber auch hervorragend zur Kommunikation zwischen Prozessen einsetzen lassen, sollten diese nicht unerwähnt bleiben. Ein Stream ist eine Art Treiber im Kernel, der allerdings keinem physikalischen Gerät zugeordnet wird. Es handelt sich dabei um eine Schnittstelle zwischen der Applikation und dem Betriebssystem zum Austausch von Daten. Die Datenströme stehen bei den Streams in beiden Richtungen zur Verfügung und sind somit Vollduplex. Auf STREAMS können neben den dafür implementierten Funktionen putmsg, getmsg und poll auch die Systemfunktionen open, close, read, write und ioctl verwendet werden. Ein STREAM-Mechanismus wird aus folgenden Komponenten aufgebaut:

gp  STREAM-Kopf – Hier werden die Systemaufrufe der Message bzw. der Messages in Byte-Streams und Returnwerte verarbeitet. Ein Message-STREAM kann Daten oder/und Statusinformationen enthalten. Der Kopf beinhaltet wie jede STREAM-Komponente eine Lese- und eine Schreib-Queue.
gp  STREAM-Module – STREAM -Module sind optional. Es gibt eine Menge vorhandener Module, deren Aufgabe es u. a. sein kann, Transformationen der Nachrichten durchzuführen. Eingetragen werden die Module mit der Funktion ioctl(). Natürlich können (fast) beliebig viele Steuermodule eingetragen werden – wobei die Eintragung eines neuen Moduls nach dem Stack-Prinzip vorgeht.
gp  STREAM-End – Besteht aus Gerätetreibern, was ein Gerätetreiber sein kann, ein Pseudotreiber, ein Terminalgerätetreiber oder ein Multiplexer.

Abbildung
Hier klicken, um das Bild zu vergrößern

Abbildung 9.4    Kommunikationsmodell eines STREAMS


Die STREAMS werden allerdings im Buch nicht behandelt.


Rheinwerk Computing

9.1.7 Sockets  downtop

Da die Netzwerkprogrammierung ein eigenes Kapitel erhält, sei das Socket hier nur kurz der Vollständigkeit halber erwähnt. Sockets wurden im Berkeley-UNIX-System (BSD) als Kommunikation zwischen verschiedenen Rechnern über ein Netzwerk eingeführt. Allerdings ist es mit den Sockets ebenso wie mit STREAMS möglich, eine Kommunikation zwischen Prozessen auf demselben Rechner auszuführen. Sockets können zur Kommunikation verschiedenste Protokolle benutzen, z. B. TCP/IP, UDP/IP oder UNIX Domain Sockets.


Rheinwerk Computing

9.1.8 Lock Files (Sperrdateien)  downtop

Eine recht primitive Form der IPC sind so genannte Lock Files (Sperrdateien), nicht zu verwechseln mit den Dateisperren (Record Locking). Dabei werden mehrere Prozesse mithilfe einer einfachen (Sperr-)Datei synchronisiert. Es wird praktisch eine Datei (meist im Verzeichnis /tmp) angelegt, worauf ein Prozess nur Schreibrechte hat. Die Synchronisation erfolgt jetzt durch einen anderen Prozess, der ebenfalls versucht, dieselbe Datei mit Schreibrechten anzulegen. Schlägt dieser Versuch fehl, existiert gerade eine entsprechende Datei. Dieses Fehlschlagen stellt praktisch die Sperre für den Prozess dar. Jetzt wartet der abgewiesene Prozess eine gewisse Zeit (meistens mit einem simplen sleep-Aufruf), bevor dieser erneut versucht, eine Datei mit entsprechenden Namen und mit Schreibrechten anzulegen. Die Freigabe dieser Sperre erfolgt dann durch den entsprechenden Prozess, der diese Datei erzeugt hat, über das Freigeben der Sperrdatei mit unlink().

So toll sich dies in der Theorie anhören mag, in der Praxis ist diese Form der IPC nur bedingt tauglich. Zum einen ist das Problem, sobald der Superuser hier mitspielen will, funktioniert die Synchronisation nicht mehr, da dieser immer schreiben darf. Und zum anderen wird hier mit dem aktiven Warten durch das Verschwenden von Prozessorzyklen (mit z. B. sleep()) nicht garantiert, welcher Prozess als nächster Zugriff auf die Sperrdatei hat. So kann es passieren, dass ein Prozess eventuell nie zum Zuge kommt.


Rheinwerk Computing

9.1.9 Dateisperren (Record Locking)  toptop

Ebenso wie mit den eben vorgestellten Sperrdateien kann man auch mit den Dateisperren eine IPC einrichten. Auf die Dateisperren (Record Locking) wurde bereits eingegangen, weshalb das Thema hier nur kurz angerissen wird.

Man unterscheidet generell zwischen Pflichtsperren (Mandatory Locks) und Kooperationssperren (Advisory Locks). Bei den Pflichtsperren werden die Zugriffsrechte einer Datei so gesetzt, dass hierbei jeder read()- und write()-Systemaufruf prüfen muss, ob dieser unter den gegenwärtig gesetzten Sperren seinen lesenden oder schreibenden Zugriff durchführen kann. Dieser Vorgang verlangsamt logischerweise den Zugriff.

Mit den Kooperationssperren hingegen verwendet man eine Funktionsbibliothek mit Zugriffsfunktionen, die Ihnen z. B. Informationen zu einer Sperre liefern und/oder den Zugriff auf gemeinsame Datenbestände »regeln«. Im Wesentlichen werden diese Sperren mit dem Systemaufruf fcntl() oder der Bibliotheksfunktion lockf() geregelt. Beide Funktionen wurden bereits in Kapitel 2 erwähnt und z. T. verwendet.

Zwar ist diese Form der IPC immer noch besser als die Sperrdateien, aber auch hierbei gibt es einige Nachteile zu beklagen. Zum einen sind die Sperrarten wenig genormt – POSIX z. B. kennt keine Mandatory Locks. Des Weiteren funktionieren die Sperren nicht bei open()- und unlink()-Aufrufen. Und das Schlimmste: Ist ein Anwender nicht gut gelaunt, kann dieser mit Absicht durch Setzen und Halten einer Lesesperre auf die komplette Datei den schreibenden Zugriff aller anderen Anwender blockieren.

 << zurück
  
  Zum Rheinwerk-Shop
Neuauflage: Linux-UNIX-Programmierung
Neuauflage:
Linux-UNIX-
Programmierung

bestellen
 Ihre Meinung?
Wie hat Ihnen das Openbook gefallen?
Ihre Meinung

 Buchtipps
Zum Rheinwerk-Shop: Linux-Server






 Linux-Server


Zum Rheinwerk-Shop: Das Komplettpaket LPIC-1 & LPIC-2






 Das Komplettpaket
 LPIC-1 & LPIC-2


Zum Rheinwerk-Shop: Linux-Hochverfügbarkeit






 Linux-
 Hochverfügbarkeit


Zum Rheinwerk-Shop: Shell-Programmierung






 Shell-
 Programmierung


Zum Rheinwerk-Shop: Linux Handbuch






 Linux Handbuch


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
Info





Copyright © Rheinwerk Verlag GmbH 2006
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern