Sehr Umfangreiche Webseite zum Programmieren in C Perl CGI, Skripting, Linux, SystemprogrammierungC C++ C/C++ ANSI C Linux Linuxsystemprogrammierung Unix Pipe FIFO System-V IPC Linuxsystemprogrammieren Interprozesskommunikation IPC Pipe FIFO System-V IPC 5. Interprozesskommunikationen (IPC)

5.1. Grundlagen - Was sind IPC's und wofür werden sie benötigt           zurück Ein Kapitel tiefer zum Inhaltsverzeichnis

Interprozesskommunikation (kurz IPC) werden dort eingesetzt, wo mindestens zwei oder mehrere Prozesse miteinander Kommunizieren sollen. Sprich Daten untereinander austauschen oder parallel bestimmte Arbeiten erledigen.

Einfache Interprozesskommunikationen kann man mit Hilfe von ...

...erstellen. Signale haben Sie ja bereits im Kapitel zuvor kennen gelernt. Über die Pipes werden Sie auch am meisten hier erfahren. Daneben existieren noch komplexe IPC's wie ...

Wenn mehrere Prozesse gemeinsam auf Daten zugreifen, kann es sehr schnell chaotisch werden. Daher sollen Probleme nicht unerwähnt bleiben, die es gilt mit Hilfe von IPC zu vermeiden. Beispielsweise ...

Dies war nun ein kurzer Einblick in die sehr Umfangreiche Welt der IPC's. Diese einzelnen Probleme die hier geschildert wurden gilt es nun in der Praxis zu erkunden.

5.2.1. Pipes - pipe()           zurück Ein Kapitel tiefer Ein Kapitel höher zum Inhaltsverzeichnis

Pipes wie auch FIFOs sind die einzigen beiden IPC die garantiert auf jedem System vorhanden sind. Also sowohl POSIX, SVR4 und BSD - Konform.

Eine Pipe ist ein undirektionaler Kommunikationskanal zwischen zwei verwandten Prozessen. Sie haben Pipes schon des öfteren in der Konsole verwendet. Beispielsweise ...

ps x | less

Hiermit haben Sie in der Shell zwei Prozesse gestartet. Ein Prozess führt ps x aus und schreibt seine Ausgabe an die Standardausgabe. Durch die Pipe '|' wird diese Standardausgabe an den Prozess less gesendet. Dieser ließt von seiner Standardeingabe, in unserem Fall von ps x, ein.

Eine Pipe dient außer der Kommunikation zwischen zwei Prozessen hervorragend zur Flusskontrolle. Dies daher, weil eine Pipe nur eine bestimmte Menge an Daten aufnehmen kann (normalerweise 4KByte). Ist die Pipe voll, wird ein Prozess mindestens solange angehalten, bis mindestens 1 Byte aus der vollen Pipe gelesen wurden und wieder Platz für diese ist, um sie wieder mit Daten zu befüllen. Andersherum, das selbe Bild, ist die Pipe leer wird der lesende Prozess so lange angehalten, bis der Schreibende Prozess etwas in diese Pipe schickt.

Sie konnten sich nun schon ein Bild zu den Pipes machen. Es gibt also ein Schreibseite und eine Leseseite. Es ist also nur eine Kommunikation in einer Richtung möglich (Halbduplex). Sie können sich unsere Beispiel oben so vorstellen ...

Pipe Halbduplex

Anhand dieses Beispiels können Sie sehen wie die Standardausgabe des Aufrufs ps x durch eine Pipe an less weitergeleitet wird. less verfährt dann weiter wie gewohnt.

Dies stellt also das Prinzip einer Pipe da. Folglich können Sie auch an der Pipe erkennen, dass diese zwei Diskriptoren besitzt. Der erste Deskriptor dient zum lesen und der zweite Dekriptor dient dazu in die Pipe zu schreiben.

Folglich sieht der Syntax wie Sie eine Pipe in C erzeugen können, folgendermaßen aus ...

#include <unistd.h>

int pipe(int fd[2]);

Also öffnet man die Pipe mit zwei Filedeskriptoren ...

Aus unserm Beispiel in der Shell ergibt dies folgendes Bild ...

Pipes Halbduplex

Hiermit können Sie aber immer noch nicht zwei Prozesse kommunizieren. Es fehlt also noch eine Rückleitung. Und diese erstellen wir mit einem fork-Aufruf.

Nun haben wir zwei Prozesse. Der Kindprozess erbt nun von seinem Elternprozess beide offenen Filedeskriptoren. Um nun den Prozessen klar zu machen, wer wohin schreibt und wer was liest, wird durch geschicktes Öffnen und Schließen der Standardkanäle bestimmt, wer aus der Pipe liest und wer etwas reinschreibt. Dabei habe Sie zwei Möglichkeiten ...

Sollte einer der Prozesse in einer Pipe Schreiben obwohl diese für das Lesen bestimmt war, wird das Signal SIGPIPE ausgelöst. Dies sollten sie Abfangen, ansonsten wird der Prozeß beendet.

Somit könnte sich folgendes Bild ergeben ...

Pipe

Nun wollen wir uns auch mal ein Beispiel dazu ansehen, wie das ganze in der Praxis funktioniert ...

#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>

#define USAGE printf("usage : %s Datei\n",argv[0]);
#define MAX 4096

int main(int argc, char *argv[])
{
  int fd[2], fd1,i, n;
  pid_t pid;
  char puffer[MAX];
  FILE *datenzeiger;

  if(argc !=2)
    { USAGE; exit(0); }

  if((fd1=open(argv[1], O_RDONLY)) < 0)
    {  perror("open : "); exit(0); }

/*Wir erstellen eine pipe*/
  if(pipe(fd) < 0)
    {  perror("pipe : "); exit(0); }

/*Wir erzeugen einen neuen Prozess*/
  if((pid=fork()) < 0)
    {  perror("pipe : "); exit(0); }

  else if(pid > 0) /*Elternprozess*/
    {
      close(fd[0]); /*Leseseite schließen*/
      n=read(fd1, puffer, MAX);
      if((write(fd[1], puffer, n)) != n)
          { perror(" write : "); exit(0); }
      if((waitpid(pid, NULL, 0)) < 0)
          { perror("waitpid : "); exit(0); }
    }
  else /*Kindprozess*/
    {
      close(fd[1]); /*Schreibseite schließen*/
      n=read(fd[0], puffer, MAX);
      if((write(STDOUT_FILENO, puffer, n)) != n)
         { perror(" write : "); exit(0); }
    }
  exit(0);
}

Hier erzeugen Sie eine Pipe und schließen die Leseseite der Eltern und die Schreibseite des Kindes. Das Kind ließt eine Datei, die Sie mit als Argument in der Kommandozeile angeben und der Elternprozess gibt dies auf die Standardausgabe aus. Da es auch einen anderen Stil gibt dies durchzuführen, will ich ihn diesen nicht vorenthalten ...

Beispielsweise im Elternprozess ...

w=dup(fd[1]); /*Duplizieren von Schreibseite*/
close(fd[0]); close(fd[1]); /*Schließen beider Deskriptoren*/
n=read(fd1,puffer,MAX);
write(w,puffer,n); /*Schreiben in die Pipe*/

Durch das duplizieren des Filedeskriptors in diesem Beispiel, bewirkt das Sie denn Orginaldeskriptor schließen und mit der Kopie weiterarbeiten. Ansonsten stellt dies keine Verbesserung oder Verschlechterung da. Diese Möglichkeit wird häufig und gerne eingesetzt in dem man zwei Pipes erzeugt und beide Prozesse sowohl lesen als auch schreiben können. Wir kommen anschließend noch dazu.

5.2.2. Pipes und Systemaufrufe oder alternativ Standard-E/A-Funktionen  zurück Ein Kapitel tiefer Ein Kapitel höher zum Inhaltsverzeichnis

Da die Systemaufrufe write, read und close anders als sonst reagieren, bedürfen diese einer kurzen Erklärung.

read
read wird solange Blockiert, bis sich wieder genügend Daten in der Pipe befinden. Schreibt kein Prozess mehr in die Pipe, bleibt read solange stecken bis der schreibende Prozess den Systemaufruf close verwendet hat. Dieses stecken bleiben von read eignet sich prima zum Synchronisieren von Prozessen.

write
write schreibt die Daten in der richtigen Reihenfolge in die Pipe. Ist die Pipe voll, wird der Schreibende Prozess solange angehalten bis wieder genügend Platz vorhanden ist. Diese Verhalten könnten Sie abschalten in dem sie das Flag O_NONBLOCK mit z.B. der Funktion fcntl setzen. In diesem Fall liefert der Schreibende Prozess 0 zurück.

Standard-E/A-Funktionen mit pipe
Natürlich ist es auch möglich auf Pipes mit Standard E/A-Funktionen zuzugreifen. Dazu müssen Sie nur die mit dem pipe()-Aufruf erhaltenen Filedeskriptoren mit der Funktion fdopen (Siehe Kapitel Low-Level-I/O) einen Dateizeiger (FILE *) zuteilen. Natürlich müssen Sie fdopen mit dem richtigen Modus verwenden. Denn es ist nicht möglich ...

FILE *f;
f=fdopen(fd[0], "w"); /*falsch*/

...zu verwenden, da fd[0] für das Lesen aus einer Pipe steht. Richtig ist dagegen ...

FILE *reading, *writing;

reading=fdopen(fd[0], "r");
writing =fdopen(fd[1], "w");

Geben Sie Ihrem Dateizeiger einfach einen Aussagekräftigen Namen um Verwechslungen auszuschließen. Sehen wir uns dazu wieder ein Beispiel an ...

#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>

#define USAGE printf("usage : %s Datei\n",argv[0]);
#define MAX 4096

int main(int argc, char *argv[])
{
  int fd[2], fd1,i, n;
  pid_t pid;
  char puffer[MAX];
  FILE *reading, *writing, *newfile;

  if(argc !=2)
    { USAGE; exit(0); }

  if((newfile=fopen(argv[1], "a+")) < 0)
    { perror("fopen : "); exit(0); }

/*Wir erstellen eine pipe*/
  if(pipe(fd) < 0)
    { perror("pipe : "); exit(0); }

/*Wir erzeugen einen neuen Prozess*/
  if((pid=fork()) < 0)
    { perror("pipe : "); exit(0); }

  else if(pid > 0) /*Elternprozess*/
   {
     close(fd[0]); /*Leseseite schließen*/
     if((writing=fdopen(fd[1], "w")) == NULL)
       { perror(" fdopen : "); exit(0); }
     fgets(puffer, MAX, stdin);
     fputs(puffer, writing); /*Wir schreiben in die Pipe*/
   }
  else /*Kindprozess*/
   {
     close(fd[1]); /*Schreibseite schließen*/
     if((reading=fdopen(fd[0], "r")) == NULL)
        { perror(" fdopen : "); exit(0); }
     fgets(puffer, MAX, reading); /*Wir lesen aus der Pipe*/
     fputs(puffer, newfile);
   }
  exit(0);
}  if((newfile=fopen(argv[1], "a+")) < 0)
    { perror("fopen : "); exit(0); }

/*Wir erstellen eine pipe*/
  if(pipe(fd) < 0)
    { perror("pipe : "); exit(0); }

/*Wir erzeugen einen neuen Prozess*/
  if((pid=fork()) < 0)
    { perror("pipe : "); exit(0); }

  else if(pid > 0) /*Elternprozess*/
   {
     close(fd[0]); /*Leseseite schließen*/
     if((writing=fdopen(fd[1], "w")) == NULL)
       { perror(" fdopen : "); exit(0); }
     fgets(puffer, MAX, stdin);
     fputs(puffer, writing); /*Wir schreiben in die Pipe*/
   }
  else /*Kindprozess*/
   {
     close(fd[1]); /*Schreibseite schließen*/
     if((reading=fdopen(fd[0], "r")) == NULL)
        { perror(" fdopen : "); exit(0); }
     fgets(puffer, MAX, reading); /*Wir lesen aus der Pipe*/
     fputs(puffer, newfile);
   }
  exit(0);
}

Mit diesem Programm öffnen bzw. erzeugen Sie eine Datei die Sie als Argument in der Kommandozeile angegeben haben. Dann schließen Sie im Elternprozess die Leseseite der Pipe und teilen den FILE-Zeiger mit fdopen der Schreibseite der Pipe zu. Dann lesen Sie aus der Standardeingabe mit fgets und danach schreiben Sie mit fputs in die Pipe. Der Kindprozess schließt dann die Pipe und teilt dem FILE-Zeiger die Leseseite von der Pipe zu. Anschließend lesen Sie aus der Pipe mit fgets und schreiben dies mit fputs in die neue Datei.

5.2.3. Mulitplexing - I/O mit Pipes           zurück Ein Kapitel tiefer Ein Kapitel höher zum Inhaltsverzeichnis

Natürlich können Sie mit Pipes auch eine Multiplexing-Ein/Ausgabe realisieren. In diesem Fall müssen Sie zwei Pipes erzeugen.

#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>

#define MAX 512

enum {READ,WRITE};

int main(int argc, char **argv)
{
 char *fname;
 int fdout[2], fdin[2];
 int fd, n;
 char puffer[MAX];

 /*Wir erzeugen 2 Pipes*/
 if(pipe(fdout) < 0 || pipe(fdin) < 0)
  { perror("pipe : "); exit(0); }

 /*Nun erzeugen wir einen neuen Prozeß*/
 switch(fork())
  {
   case -1  :  perror("fork : "); exit(0);
   case  0  :  /*Im Kindprozess*/
               close(0); /*stdin schließen*/
               dup(fdout[READ]); /*Lese-Deskriptor duplizieren*/
               close(1); /*stdout schließen*/
               dup(fdin[WRITE]); /*Schreib-Deskriptor duplizieren*/
               /*Kindprozess wird von grep überlagert daher benötigen wir diese
               Deskriptoren nicht mehr. Alle Vorsorgen haben wir eben gemacht...*/
               close(fdout[READ]);
               close(fdout[WRITE]);
               close(fdin[READ]);
               close(fdin[WRITE]);

               /*Nach dieser Zeile wird der Kindprozeß überlagert....*/
               execlp("grep", "grep", "^#include", NULL);

   default  :  /*Elternprozeß*/
               close(fdout[READ]); /*Elternprozeß schreibt nur in die Pipe fdout*/
               close(fdin[WRITE]); /*Elternprozeß liest nur von Pipe fdin aus grep*/

               /*Datei öffnen*/
               fd=open(argv[1], O_RDONLY);
               if(fd < 0)
                 {  perror("open : "); exit(0); }

               /*Aus Datei lesen*/
               while((n = read(fd, puffer, MAX)) != 0)
                {
                  if(n==-1)
                   exit(0);
                  /*Wir schreiben etwas in die Pipe fdout an grep*/
                  if(write(fdout[WRITE], puffer, n) == -1)
                     {  perror("write : "); exit(0); }
                }
               /*Datei schließen*/
               close(fd);
               /*Schreibseite schließen*/
               close(fdout[WRITE]);
               /*Das ist das Zeichen für grep zum lesen aus der Pipe fdout*/
               /*Nun wollen wir lesen was grep mit unseren Daten gemacht hat*/
               /*read blockiert solange bis grep fertig ist*/
               while(( n=read(fdin[READ], puffer, MAX)) != 0)
                {
                 if(n==-1)
                   exit(0);
                 /*Jetzt wollen wir das ganze auf dem Bildschirm ausgeben*/
                 if(write(STDOUT_FILENO,puffer, n) == -1)
                    { perror("write :"); exit(0); }
                }
               /*Jetzt noch die Leseseite schließen wir sind fertig*/
               close(fdin[READ]);
  }
 return 0;
}

Entscheidend ist im Kindprozess das Duplizieren des Filedeskriptors. Wie Sie im Kapitel dup und dup2 bereits gelernt haben bleibt mit Hilfe der dup-Funktion das close-on-exec-Flag gelöscht bei einem exec-Aufruf. Wir verbinden praktisch mit der Funktion dup die Standardaus/eingabekanäle mit unseren Pipekanälen. Wichtig ist in einem solchen Fall, wenn Sie den Kindprozess überlagern die Reihenfolge: fork, close, dup, exec. Somit kommuniziert der aktuell laufender Elternprozess mit dem Programm grep und Filter alle Zeilen die mit '#include' beginnen aus und gibt diese auf dem Bildschirm aus.

Hier sehen das ganze nochmals Bildlich ...

Pipe dup

Mit zwei Pipes sind Sie außerdem in der Lage 2 Prozesse zu Synchronisieren, ähnlich wie bei dem Kapitel der Signale. In diesem Falle kommt uns das Blockieren der Funktion read zu gute. Folgendermaßen können Sie die Synchronisation von Prozesse mit Pipes erledigen ...

int eltern_kind_pipe[2],
kind_eltern_pipe[2];

void INIT_SYNC(void)
{
  if(pipe(eltern_kind_pipe) < 0 || pipe(kind_eltern_pipe) < 0)
     EXIT("Fehler beim Initialisieren der Pipes........");
}

void bin_fertig_papa(pid_t pid)
{
  if(write(kind_eltern_pipe[1], "k", 1) !=1)
     EXIT("Fehler bei write..............");
}

void bin_fertig_kind(pid_t pid)
{
  if(write(eltern_kind_pipe[1], "e", 1) != 1)
     EXIT("Fehler bei write.......");
}

void warte_auf_papa(void)
{
  char zeichen;
  if(read(eltern_kind_pipe[0], &zeichen, 1) != 1)
     EXIT("Fehler bei read..........");
  if(zeichen!='e') /*Kind erwartet das Zeichen 'e' */
     EXIT("Fehler bei warte_auf_papa......");
}


void warte_auf_kind(void)
{
  char zeichen;
  if(read(kind_eltern_pipe[0], &zeichen, 1) != 1)
     EXIT("Fehler bei read..........");
  if(zeichen!='k') /*Eltern erwarten das Zeichen 'k' */
      EXIT("Fehler bei warte_auf_kind......");
}

5.2.4. Prozesse Umleiten und Filtern           zurück Ein Kapitel tiefer zum Inhaltsverzeichnis

Im ersten Kapitel zu den Pipes haben Sie ein Programm geschrieben, dass aus einer Datei im Kindprozess gelesen hat. Dies gelesene haben Sie dann in die Pipe geschrieben und der Elternprozess hat dies dann ausgelesen und hat es dann an die Standardausgabe auf dem Bildschirm weitergeleitet. Bei etwas längeren Texten zum Ausgeben war dies etwas unpassend, da der ganze Text auf einmal Ausgegeben wurde, so das Sie nicht sehen konnten was am Anfang des Textes stand. Nun könnte man natürlich hergehen und eine Funktion schreiben bei der der Anwender alle 23 Zeilen aufgefordert wird eine Taste zu drücken, damit die nächsten 23 Zeilen gelesen werden können. Sicherlich es wäre ein gute Übung aber wozu das Rad wieder neu erfinden. Unix stellt uns dafür doch Funktionen wie z.B. less, pg, nl, tail, oder more zur Verfügung womit Sie sich Zeilen-bzw. Seitenweise durch den Text blättern können. Wir wollen nun im folgendem Programm den Kindprozess mit dem Programm less überlagern ...

#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
#include <signal.h>


#define EXIT(s) {fprintf(stderr, "%s",s); exit(0);}
#define USAGE(s) {fprintf(stderr, "%s Datei zum lesen\n",s); exit(0);}
#define MAX 8192

enum {ERROR=-1,SUCCESS};

int main(int argc, char **argv)
{
  int fd[2];
  pid_t pid;
  FILE *pipe_writer, *file;
  char puffer[MAX];

  if(argc!=2)
     USAGE(argv[0]);

  if((file=fopen(argv[1], "r")) == NULL)
     EXIT("Fehler bei fopen.........\n");

  if(pipe(fd) == ERROR)
     EXIT("Fehler bei pipe.......\n");

  if((pid=fork()) == ERROR)
     EXIT("Fehler bei fork......\n");
  if(pid > 0) /*Elternprozess*/
   {
     close(fd[0]); /*Wir schließen die Leseseite*/
     if((pipe_writer = fdopen(fd[1], "w")) == NULL)
        EXIT("Fehler bei fdopen............\n");

     fread(&puffer, MAX, 1, file);
     fputs(puffer, pipe_writer); /*Wir schreiben in die Pipe*/

     fclose(pipe_writer);
     /*Mit dem schließen der Schreibseite */
     /*teilen wir dem Kindprozess das Ende*/
     /*des Schreiben in die Pipe mit*/
     if(waitpid(pid,NULL,0) == ERROR)
         EXIT("Fehler bei waitpid.........\n");
     exit(0);
   }
  else /*Kindprozess ließt aus der pipe mit less*/
   {
     sleep(1);
     close(fd[1]); /*Schließen der Schreibseite*/
     if(fd[0] != STDIN_FILENO) /*muss überprüft werden*/
       {
 /*duplizieren Standardeingabe*/
         if(dup2(fd[0], STDIN_FILENO) != STDIN_FILENO)
                EXIT("Fehler bei dup2......\n");
         close(fd[0]); /*Wird nicht mehr benötigt*/
       }
     if(execl("/usr/bin/less", "less", NULL) == ERROR) ;
       EXIT("Fehler bei execl.......\n");
   }
}

Sehen Sie sich dieses Beispiel anhand einer Grafik an ...

Pipe

Sie sehen auf einmal bekommen all die Funktionen, die Sie bereits kennen langsam Ihren Sinn. Wichtig ist auch, dass Sie bei der Standardeingabe, dem Kindprozess die Leseseite der Pipe einrichten und mit einer exec-Funktion den Kindprozess überlagern.

Wenn sie sich die Grafik oben nochmals ansehen werden Sie sich Fragen, ob man da nicht noch andere Programme dazwischenhängen kann? Ja, den Pipes eignen sich Ideal für Filterprogramme. Zum Beispiel einen Druckerfilter um einen Text formatiert auszugeben oder alle Buchstaben GROSS zu schreiben. Wir wollen einen Filter schreiben der bestimmte Wörter ausfiltert und sie im Text dann als xxx angibt. Hier nun das Filterprogramm ...

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#define MAX 255

char *filterwords[] = { "und" , "oder", "aber", "ein", NULL};
char token[] = " ";

void output(char *puffer)
{  int i=0,len;
  static int merker=0;
  char *p,tmp[20];

  p=strtok(puffer, token); /*Trennen die einzelnen Wörter*/
  while(p != NULL)
   {     i=0;
     merker=0;
     while(filterwords[i]!=NULL)
       {
 /*Wir vergleichen alle Wörter die wir ausfiltern wollen
 mit dem aktuellen Wort auf das p zeigt*/
          if(strstr(p, filterwords[i]) != NULL)
           {
             len=strlen(filterwords[i]);
             /*Wir zählen die Länge des zu ersetzenden Wortes*/
             memset(tmp, 'x', len);
             /*Jetzt schreiben wir len x in das Array tmp*/
             fputs(tmp,stdout); /*Nun geben wir len x auf den Bildschirm aus*/
             fputs(" ",stdout); /*Leerzeichen*/
             merker=1; /*Merker ob schon etwas ausgegeben wurde*/
           }
         i++;
       }
     if(merker==0) {
 /*Normale Ausgabe da nichts gefiltert wurde*/
     fputs(p,stdout);
     fputs(" ",stdout); /*Leerzeichen*/
       }
     p=strtok(NULL, token);
   }
}

int main()
{
  char pufferin[MAX];

  while(fgets(pufferin, MAX, stdin))
     output(pufferin);

  return 0;
}

Ich habe folgende Filterwörter verwendet ...

char *filterwords[] = { "und" , "oder", "aber", "ein", NULL};

Diese Worte werden alle mit xxx ausgegeben. Dies Programm können Sie beliebig anpassen. Z.B. zur Vermeidung von vulgären Ausdrücken wenn man Schimpfmails erhält. Wenn Sie eine ganze Datenbank von Ausfilterungswörtern erstellen wollen, rate ich Ihnen zu einer dynamischen Struktur in Form von Verketteter Listen oder Hashes. Sie könnten so in etwa natürlich auch ein Fremdsprachenübersetzungsprogramm schreiben. Wir wollen jetzt unser Programm vom Kapitel zuvor mit einem Filterprogramm anpassen ...

#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
#include <signal.h>


#define EXIT(s) {fprintf(stderr, "%s",s); exit(0);}
#define USAGE(s) {fprintf(stderr, "%s Datei zum lesen\n",s); exit(0);}
#define MAX 8192

enum {ERROR=-1,SUCCESS};

int main(int argc, char **argv)
{
  int fd[2];
  pid_t pid;
  FILE *pipe_writer, *file;
  char puffer[MAX];

  if(argc!=2)
     USAGE(argv[0]);

  if((file=fopen(argv[1], "r")) == NULL)
     EXIT("Fehler bei fopen.........\n");

  if(pipe(fd) == ERROR)
      EXIT("Fehler bei pipe.......\n");

  if((pid=fork()) == ERROR)
     EXIT("Fehler bei fork......\n");
  if(pid > 0) /*Elternprozess*/
   {
     close(fd[0]); /*Wir schließen die Leseseite*/
     if((pipe_writer = fdopen(fd[1], "w")) == NULL)
       EXIT("Fehler bei fdopen............\n");

     fread(&puffer, MAX, 1, file);
     fputs(puffer, pipe_writer); /*Wir schreiben in die Pipe*/
     /*Mit dem schließen der Schreibseite*/
     /*teilen wir dem Kindprozess das Ende*/
     /*des Schreiben in die Pipe mit*/
      fclose(pipe_writer);
     if(waitpid(pid,NULL,0) == ERROR)
       EXIT("Fehler bei waitpid.........\n");
     exit(0);
    }
  else /*Kindprozess ließt aus der pipe mit less*/
   {
     sleep(1);
     close(fd[1]); /*Schließen der Schreibseite*/
     if(fd[0] != STDIN_FILENO) /*muss überprüft werden*/
       {
/*duplizieren Standardeingabe*/
        if(dup2(fd[0], STDIN_FILENO) != STDIN_FILENO)
            EXIT("Fehler bei dup2......\n");
        close(fd[0]);
       }
     if(execl("./filter", "filter", NULL) == ERROR) ;
       EXIT("Fehler bei execl.......\n");
   }
}

Sie mussten eigentlich nur eine Zeile anpassen. Nämlich die, zur Überlagerung unseres neuem Programms im Kindprozess, die execl-Funktion. Nun führen Sie das Programm an einer Längeren Textdatei aus und schon funktioniert unser Filter.

Weiter mit 5.2.5. popen() - Pipes etwas komfortabler