Prozessübergreifende Kommunikation - Pipes

Pipe ist ein Kommunikationsmedium zwischen zwei oder mehr verwandten oder miteinander verbundenen Prozessen. Dies kann entweder innerhalb eines Prozesses oder einer Kommunikation zwischen dem untergeordneten und dem übergeordneten Prozess erfolgen. Die Kommunikation kann auch mehrstufig sein, z. B. die Kommunikation zwischen dem Elternteil, dem Kind und dem Enkelkind usw. Die Kommunikation wird durch einen Prozess erreicht, der in die Pipe schreibt, und durch einen anderen Prozess, der aus der Pipe liest. Um den Pipe-Systemaufruf zu erreichen, erstellen Sie zwei Dateien, eine zum Schreiben in die Datei und eine zum Lesen aus der Datei.

Der Rohrmechanismus kann mit einem Echtzeitszenario betrachtet werden, z. B. das Einfüllen von Wasser mit dem Rohr in einen Behälter, beispielsweise einen Eimer, und das Abrufen von Wasser, beispielsweise mit einem Becher. Der Füllvorgang ist nichts anderes als das Schreiben in das Rohr und der Lesevorgang ist nichts anderes als das Abrufen aus dem Rohr. Dies bedeutet, dass ein Ausgang (Wasser) für den anderen (Eimer) eingegeben wird.

#include<unistd.h>

int pipe(int pipedes[2]);

Dieser Systemaufruf würde eine Pipe für die Einwegkommunikation erstellen, dh er erstellt zwei Deskriptoren, von denen einer zum Lesen aus der Pipe und der andere zum Schreiben in die Pipe verbunden ist.

Deskriptor-Pipes [0] dienen zum Lesen und Pipes [1] zum Schreiben. Was auch immer in Pipes [1] geschrieben ist, kann aus Pipes [0] gelesen werden.

Dieser Aufruf würde bei Erfolg Null und im Fehlerfall -1 zurückgeben. Um die Fehlerursache zu ermitteln, überprüfen Sie die Funktion errno variable oder perror ().

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

Obwohl die grundlegenden Vorgänge für Dateien Lesen und Schreiben sind, ist es wichtig, die Datei zu öffnen, bevor Sie die Vorgänge ausführen und die Datei nach Abschluss der erforderlichen Vorgänge schließen. Normalerweise werden standardmäßig 3 Deskriptoren für jeden Prozess geöffnet, die für die Eingabe (Standardeingabe - stdin), Ausgabe (Standardausgabe - stdout) und Fehler (Standardfehler - stderr) mit den Dateideskriptoren 0, 1 bzw. 2 verwendet werden.

Dieser Systemaufruf würde einen Dateideskriptor zurückgeben, der für weitere Dateivorgänge von Lesen / Schreiben / Suchen (lseek) verwendet wird. Normalerweise beginnen Dateideskriptoren bei 3 und erhöhen sich mit der Anzahl der geöffneten Dateien um eine Zahl.

Die Argumente, die an den offenen Systemaufruf übergeben werden, sind Pfadname (relativer oder absoluter Pfad), Flags, die den Zweck des Öffnens der Datei angeben (z. B. Öffnen zum Lesen, O_RDONLY, Schreiben, O_WRONLY, Lesen und Schreiben, O_RDWR, Anhängen an die vorhandene Datei O_APPEND, um eine Datei zu erstellen, falls diese nicht mit O_CREAT usw. vorhanden ist) und der erforderliche Modus, der Berechtigungen zum Lesen / Schreiben / Ausführen für Benutzer oder Eigentümer / Gruppe / andere bereitstellt. Der Modus kann mit Symbolen angegeben werden.

Lesen - 4, Schreiben - 2 und Ausführen - 1.

Beispiel: Oktalwert (beginnt mit 0), 0764 impliziert, dass der Eigentümer Lese-, Schreib- und Ausführungsberechtigungen hat, die Gruppe Lese- und Schreibberechtigungen hat, andere Leseberechtigungen haben. Dies kann auch als S_IRWXU | dargestellt werden S_IRGRP | S_IWGRP | S_IROTH, was bedeutet oder Betrieb von 0700 | 0040 | 0020 | 0004 → 0764.

Dieser Systemaufruf gibt bei Erfolg die neue Dateideskriptor-ID und -1 im Fehlerfall zurück. Die Fehlerursache kann mit der Funktion errno variable oder perror () identifiziert werden.

#include<unistd.h>

int close(int fd)

Der obige Systemaufruf schließt bereits geöffneten Dateideskriptor. Dies bedeutet, dass die Datei nicht mehr verwendet wird und die zugehörigen Ressourcen von jedem anderen Prozess wiederverwendet werden können. Dieser Systemaufruf gibt bei Erfolg Null und im Fehlerfall -1 zurück. Die Fehlerursache kann mit der Funktion errno variable oder perror () identifiziert werden.

#include<unistd.h>

ssize_t read(int fd, void *buf, size_t count)

Der obige Systemaufruf besteht darin, aus der angegebenen Datei mit Argumenten des Dateideskriptors fd, des richtigen Puffers mit zugewiesenem Speicher (entweder statisch oder dynamisch) und der Größe des Puffers zu lesen.

Die Dateideskriptor-ID dient zur Identifizierung der jeweiligen Datei, die nach dem Aufruf des Systemaufrufs open () oder pipe () zurückgegeben wird. Die Datei muss vor dem Lesen aus der Datei geöffnet werden. Es wird automatisch geöffnet, wenn der Systemaufruf von pipe () aufgerufen wird.

Dieser Aufruf würde die Anzahl der gelesenen Bytes (oder Null, wenn das Ende der Datei erreicht wird) bei Erfolg und -1 bei einem Fehler zurückgeben. Die Rückgabebytes können kleiner sein als die Anzahl der angeforderten Bytes, nur für den Fall, dass keine Daten verfügbar sind oder die Datei geschlossen wird. Im Fehlerfall wird die richtige Fehlernummer festgelegt.

Um die Fehlerursache zu ermitteln, überprüfen Sie die Funktion errno variable oder perror ().

#include<unistd.h>

ssize_t write(int fd, void *buf, size_t count)

Der obige Systemaufruf besteht darin, mit Argumenten des Dateideskriptors fd, einem geeigneten Puffer mit zugewiesenem Speicher (entweder statisch oder dynamisch) und der Größe des Puffers in die angegebene Datei zu schreiben.

Die Dateideskriptor-ID dient zur Identifizierung der jeweiligen Datei, die nach dem Aufruf des Systemaufrufs open () oder pipe () zurückgegeben wird.

Die Datei muss geöffnet werden, bevor in die Datei geschrieben wird. Es wird automatisch geöffnet, wenn der Systemaufruf pipe () aufgerufen wird.

Dieser Aufruf würde die Anzahl der geschriebenen Bytes (oder Null, falls nichts geschrieben wird) bei Erfolg und -1 im Fehlerfall zurückgeben. Im Fehlerfall wird die richtige Fehlernummer festgelegt.

Um die Fehlerursache zu ermitteln, überprüfen Sie die Funktion errno variable oder perror ().

Beispielprogramme

Es folgen einige Beispielprogramme.

Example program 1 - Programm zum Schreiben und Lesen von zwei Nachrichten mit Pipe.

Algorithmus

Step 1 - Erstellen Sie eine Pipe.

Step 2 - Senden Sie eine Nachricht an die Pipe.

Step 3 - Rufen Sie die Nachricht aus der Pipe ab und schreiben Sie sie in die Standardausgabe.

Step 4 - Senden Sie eine weitere Nachricht an die Pipe.

Step 5 - Rufen Sie die Nachricht aus der Pipe ab und schreiben Sie sie in die Standardausgabe.

Note - Das Abrufen von Nachrichten kann auch nach dem Senden aller Nachrichten erfolgen.

Source Code: simplepipe.c

#include<stdio.h>
#include<unistd.h>

int main() {
   int pipefds[2];
   int returnstatus;
   char writemessages[2][20]={"Hi", "Hello"};
   char readmessage[20];
   returnstatus = pipe(pipefds);
   
   if (returnstatus == -1) {
      printf("Unable to create pipe\n");
      return 1;
   }
   
   printf("Writing to pipe - Message 1 is %s\n", writemessages[0]);
   write(pipefds[1], writemessages[0], sizeof(writemessages[0]));
   read(pipefds[0], readmessage, sizeof(readmessage));
   printf("Reading from pipe – Message 1 is %s\n", readmessage);
   printf("Writing to pipe - Message 2 is %s\n", writemessages[0]);
   write(pipefds[1], writemessages[1], sizeof(writemessages[0]));
   read(pipefds[0], readmessage, sizeof(readmessage));
   printf("Reading from pipe – Message 2 is %s\n", readmessage);
   return 0;
}

Note- Idealerweise muss der Rückgabestatus für jeden Systemaufruf überprüft werden. Um den Vorgang zu vereinfachen, werden nicht für alle Anrufe Überprüfungen durchgeführt.

Ausführungsschritte

Zusammenstellung

gcc -o simplepipe simplepipe.c

Ausführung / Ausgabe

Writing to pipe - Message 1 is Hi
Reading from pipe – Message 1 is Hi
Writing to pipe - Message 2 is Hi
Reading from pipe – Message 2 is Hell

Example program 2 - Programm zum Schreiben und Lesen von zwei Nachrichten über die Pipe mithilfe des übergeordneten und des untergeordneten Prozesses.

Algorithmus

Step 1 - Erstellen Sie eine Pipe.

Step 2 - Erstellen Sie einen untergeordneten Prozess.

Step 3 - Übergeordneter Prozess schreibt in die Pipe.

Step 4 - Der untergeordnete Prozess ruft die Nachricht aus der Pipe ab und schreibt sie in die Standardausgabe.

Step 5 - Wiederholen Sie Schritt 3 und Schritt 4 noch einmal.

Source Code: pipewithprocesses.c

#include<stdio.h>
#include<unistd.h>

int main() {
   int pipefds[2];
   int returnstatus;
   int pid;
   char writemessages[2][20]={"Hi", "Hello"};
   char readmessage[20];
   returnstatus = pipe(pipefds);
   if (returnstatus == -1) {
      printf("Unable to create pipe\n");
      return 1;
   }
   pid = fork();
   
   // Child process
   if (pid == 0) {
      read(pipefds[0], readmessage, sizeof(readmessage));
      printf("Child Process - Reading from pipe – Message 1 is %s\n", readmessage);
      read(pipefds[0], readmessage, sizeof(readmessage));
      printf("Child Process - Reading from pipe – Message 2 is %s\n", readmessage);
   } else { //Parent process
      printf("Parent Process - Writing to pipe - Message 1 is %s\n", writemessages[0]);
      write(pipefds[1], writemessages[0], sizeof(writemessages[0]));
      printf("Parent Process - Writing to pipe - Message 2 is %s\n", writemessages[1]);
      write(pipefds[1], writemessages[1], sizeof(writemessages[1]));
   }
   return 0;
}

Ausführungsschritte

Compilation

gcc pipewithprocesses.c –o pipewithprocesses

Execution

Parent Process - Writing to pipe - Message 1 is Hi
Parent Process - Writing to pipe - Message 2 is Hello
Child Process - Reading from pipe – Message 1 is Hi
Child Process - Reading from pipe – Message 2 is Hello

Zweiwege-Kommunikation über Rohre

Die Rohrkommunikation wird nur als Einwegkommunikation angesehen, dh entweder der übergeordnete Prozess schreibt und der untergeordnete Prozess liest oder umgekehrt, aber nicht beide. Was ist jedoch, wenn sowohl das Elternteil als auch das Kind gleichzeitig aus den Pipes schreiben und lesen müssen? Die Lösung ist eine bidirektionale Kommunikation über Pipes. Für die bidirektionale Kommunikation sind zwei Leitungen erforderlich.

Im Folgenden sind die Schritte aufgeführt, um eine bidirektionale Kommunikation zu erreichen:

Step 1- Erstellen Sie zwei Rohre. Das erste ist, dass die Eltern schreiben und das Kind lesen, beispielsweise als pipe1. Zweitens muss das Kind schreiben und die Eltern lesen, beispielsweise als pipe2.

Step 2 - Erstellen Sie einen untergeordneten Prozess.

Step 3 - Schließen Sie unerwünschte Enden, da für jede Kommunikation nur ein Ende benötigt wird.

Step 4 - Schließen Sie unerwünschte Enden im übergeordneten Prozess, lesen Sie das Ende von Pipe1 und schreiben Sie das Ende von Pipe2.

Step 5 - Schließen Sie die unerwünschten Enden im untergeordneten Prozess, schreiben Sie das Ende von Pipe1 und lesen Sie das Ende von Pipe2.

Step 6 - Führen Sie die Kommunikation nach Bedarf durch.

Beispielprogramme

Sample program 1 - Erreichen einer bidirektionalen Kommunikation über Pipes.

Algorithmus

Step 1 - Erstellen Sie pipe1, damit der übergeordnete Prozess schreiben und der untergeordnete Prozess lesen kann.

Step 2 - Erstellen Sie pipe2, damit der untergeordnete Prozess schreiben und der übergeordnete Prozess lesen kann.

Step 3 - Schließen Sie die unerwünschten Rohrenden von der Eltern- und Kinderseite.

Step 4 - Übergeordneter Prozess zum Schreiben einer Nachricht und untergeordneter Prozess zum Lesen und Anzeigen auf dem Bildschirm.

Step 5 - Untergeordneter Prozess zum Schreiben einer Nachricht und übergeordneter Prozess zum Lesen und Anzeigen auf dem Bildschirm.

Source Code: twowayspipe.c

#include<stdio.h>
#include<unistd.h>

int main() {
   int pipefds1[2], pipefds2[2];
   int returnstatus1, returnstatus2;
   int pid;
   char pipe1writemessage[20] = "Hi";
   char pipe2writemessage[20] = "Hello";
   char readmessage[20];
   returnstatus1 = pipe(pipefds1);
   
   if (returnstatus1 == -1) {
      printf("Unable to create pipe 1 \n");
      return 1;
   }
   returnstatus2 = pipe(pipefds2);
   
   if (returnstatus2 == -1) {
      printf("Unable to create pipe 2 \n");
      return 1;
   }
   pid = fork();
   
   if (pid != 0) // Parent process {
      close(pipefds1[0]); // Close the unwanted pipe1 read side
      close(pipefds2[1]); // Close the unwanted pipe2 write side
      printf("In Parent: Writing to pipe 1 – Message is %s\n", pipe1writemessage);
      write(pipefds1[1], pipe1writemessage, sizeof(pipe1writemessage));
      read(pipefds2[0], readmessage, sizeof(readmessage));
      printf("In Parent: Reading from pipe 2 – Message is %s\n", readmessage);
   } else { //child process
      close(pipefds1[1]); // Close the unwanted pipe1 write side
      close(pipefds2[0]); // Close the unwanted pipe2 read side
      read(pipefds1[0], readmessage, sizeof(readmessage));
      printf("In Child: Reading from pipe 1 – Message is %s\n", readmessage);
      printf("In Child: Writing to pipe 2 – Message is %s\n", pipe2writemessage);
      write(pipefds2[1], pipe2writemessage, sizeof(pipe2writemessage));
   }
   return 0;
}

Ausführungsschritte

Zusammenstellung

gcc twowayspipe.c –o twowayspipe

Ausführung

In Parent: Writing to pipe 1 – Message is Hi
In Child: Reading from pipe 1 – Message is Hi
In Child: Writing to pipe 2 – Message is Hello
In Parent: Reading from pipe 2 – Message is Hello