Implementierung mehrerer Pipes in C

Lesezeit: 11 Minuten

Benutzeravatar von mkab
mkab

Ich versuche, mehrere Pipes in meiner Shell in C zu implementieren. Ich habe ein Tutorial dazu gefunden Webseite und die von mir erstellte Funktion basiert auf diesem Beispiel. Hier ist die Funktion

void executePipes(cmdLine* command, char* userInput) {
    int numPipes = 2 * countPipes(userInput);
    int status;
    int i = 0, j = 0;
    int pipefds[numPipes];

    for(i = 0; i < (numPipes); i += 2)
        pipe(pipefds + i);

    while(command != NULL) {
        if(fork() == 0){

            if(j != 0){
                dup2(pipefds[j - 2], 0);
            }

            if(command->next != NULL){
                dup2(pipefds[j + 1], 1);
            }    

            for(i = 0; i < (numPipes); i++){
                close(pipefds[i]);
            }
            if( execvp(*command->arguments, command->arguments) < 0 ){
                perror(*command->arguments);
                exit(EXIT_FAILURE);
            }
        }

        else{
                if(command != NULL)
                    command = command->next;

                j += 2;
                for(i = 0; i < (numPipes ); i++){
                   close(pipefds[i]);
                }
               while(waitpid(0,0,0) < 0);
        }
    }

}

Nachdem Sie es ausgeführt und einen Befehl wie zum Beispiel eingegeben haben ls | grep bin, hängt die Shell einfach dort und gibt kein Ergebnis aus. Ich habe darauf geachtet, dass ich alle Rohre geschlossen habe. Aber es hängt einfach da. Ich dachte, dass es das war waitpid das war das problem. Ich habe die entfernt waitpid und nach der Ausführung erhalte ich keine Ergebnisse. Was habe ich falsch gemacht? Danke.

Code hinzugefügt:

void runPipedCommands(cmdLine* command, char* userInput) {
    int numPipes = countPipes(userInput);

    int status;
    int i = 0, j = 0;

    pid_t pid;

    int pipefds[2*numPipes];

    for(i = 0; i < 2*(numPipes); i++){
        if(pipe(pipefds + i*2) < 0) {
            perror("pipe");
            exit(EXIT_FAILURE);
        }
    }

    while(command) {
        pid = fork();
        if(pid == 0) {

            //if not first command
            if(j != 0){
                if(dup2(pipefds[(j-1) * 2], 0) < 0){
                    perror(" dup2");///j-2 0 j+1 1
                    exit(EXIT_FAILURE);
                    //printf("j != 0  dup(pipefd[%d], 0])\n", j-2);
                }
            //if not last command
            if(command->next){
                if(dup2(pipefds[j * 2 + 1], 1) < 0){
                    perror("dup2");
                    exit(EXIT_FAILURE);
                }
            }

            for(i = 0; i < 2*numPipes; i++){
                    close(pipefds[i]);
            }

            if( execvp(*command->arguments, command->arguments) < 0 ){
                    perror(*command->arguments);
                    exit(EXIT_FAILURE);
            }
        } else if(pid < 0){
            perror("error");
            exit(EXIT_FAILURE);
        }

        command = command->next;
        j++;
    }
        for(i = 0; i < 2 * numPipes; i++){
            close(pipefds[i]);
            puts("closed pipe in parent");
        }

        while(waitpid(0,0,0) <= 0);

    }

}

  • Stilistischer Hinweis für Beiträge: Entfernen Sie den auskommentierten Code und entfernen Sie alle überflüssigen Leerzeichen.

    – tdenniston

    5. Dezember 2011 um 18:28 Uhr

  • können Sie den vollständigen Code Ihrer Implementierung posten, warum Sie Struct-Befehle verwenden mussten

    – ss321c

    27. August 2018 um 15:24 Uhr


Benutzeravatar von Christopher Neylan
Christoph Neylan

Ich glaube, das Problem hier ist, dass Sie warten und sich innerhalb derselben Schleife schließen, die Kinder erzeugt. Bei der ersten Iteration wird das Kind ausgeführt (was das Kindprogramm zerstört und es mit Ihrem ersten Befehl überschreibt), und dann schließt das Elternteil alle seine Dateideskriptoren und wartet darauf, dass das Kind fertig ist, bevor es mit der Erstellung des nächsten Kinds fortfährt . Da der Elternteil an diesem Punkt alle seine Pipes geschlossen hat, haben alle weiteren Kinder nichts zum Schreiben oder Lesen. Da Sie den Erfolg Ihrer dup2-Aufrufe nicht überprüfen, bleibt dies unbemerkt.

Wenn Sie die gleiche Schleifenstruktur beibehalten möchten, müssen Sie sicherstellen, dass der übergeordnete Dateideskriptor nur die bereits verwendeten Dateideskriptoren schließt, die nicht verwendeten jedoch in Ruhe lässt. Nachdem alle untergeordneten Elemente erstellt wurden, kann Ihr übergeordnetes Element warten.

BEARBEITEN: Ich habe Eltern/Kind in meiner Antwort verwechselt, aber die Argumentation gilt immer noch: Der Prozess, der mit der Gabelung fortfährt, schließt alle seine Kopien der Pipes, sodass jeder Prozess nach der ersten Gabelung keine gültigen Dateideskriptoren zum Lesen hat zu/von schreiben.

Pseudo-Code, der ein Array von Pipes verwendet, die im Voraus erstellt wurden:

/* parent creates all needed pipes at the start */
for( i = 0; i < num-pipes; i++ ){
    if( pipe(pipefds + i*2) < 0 ){
        perror and exit
    }
}

commandc = 0
while( command ){
    pid = fork()
    if( pid == 0 ){
        /* child gets input from the previous command,
            if it's not the first command */
        if( not first command ){
            if( dup2(pipefds[(commandc-1)*2], 0) < ){
                perror and exit
            }
        }
        /* child outputs to next command, if it's not
            the last command */
        if( not last command ){
            if( dup2(pipefds[commandc*2+1], 1) < 0 ){
                perror and exit
            }
        }
        close all pipe-fds
        execvp
        perror and exit
    } else if( pid < 0 ){
        perror and exit
    }
    cmd = cmd->next
    commandc++
}

/* parent closes all of its copies at the end */
for( i = 0; i < 2 * num-pipes; i++ ){
    close( pipefds[i] );
}

In diesem Code erstellt der ursprüngliche Elternprozess für jeden Befehl ein Kind und überlebt daher die gesamte Tortur. Die Kinder prüfen, ob sie ihre Eingabe vom vorherigen Befehl erhalten und ihre Ausgabe an den nächsten Befehl senden sollen. Dann schließen sie alle ihre Kopien der Pipe-Dateideskriptoren und führen dann exec aus. Der Elternteil tut nichts anderes als zu verzweigen, bis er für jeden Befehl ein Kind erzeugt hat. Er schließt dann alle seine Kopien der Deskriptoren und kann weiter warten.

Zuerst alle benötigten Pipes zu erstellen und sie dann in der Schleife zu verwalten, ist schwierig und erfordert einige Array-Arithmetik. Das Ziel sieht aber so aus:

cmd0    cmd1   cmd2   cmd3   cmd4
   pipe0   pipe1  pipe2  pipe3
   [0,1]   [2,3]  [4,5]  [6,7]

Wenn Sie erkennen, dass Sie zu einem bestimmten Zeitpunkt nur zwei Sätze von Pipes benötigen (die Pipe zum vorherigen Befehl und die Pipe zum nächsten Befehl), wird Ihr Code vereinfacht und etwas robuster. Ephemient gibt hier Pseudo-Code dafür an. Sein Code ist sauberer, da Eltern und Kind keine unnötigen Schleifen ausführen müssen, um nicht benötigte Dateideskriptoren zu schließen, und weil die Eltern ihre Kopien der Dateideskriptoren unmittelbar nach dem Fork schließen können.

Als Randbemerkung: Sie sollten immer die Rückgabewerte von pipe, dup2, fork und exec überprüfen.

BEARBEITEN 2: Tippfehler im Pseudocode. OP: num-pipes wäre die Anzahl der Pipes. ZB “ls | grep foo | sort -r” hätte 2 Pipes.

  • Danke für Ihre Hilfe. Ich mag verzweifelt klingen, aber können Sie mir einen Pseudocode geben, der das nachahmen kann, was Ihrer Meinung nach das Problem ist? Ich habe alles versucht, was ich konnte, aber ich habe immer noch irgendwo ein Speicherleck. Ich sehe einfach nicht, wo das Problem ist. Danke

    – mkab

    7. Dezember 2011 um 11:37 Uhr


  • Danke für den Pseudocode. In Ihrem Code tut num-pipe mittlere Anzahl von Rohren? Denn wenn es das ist, würde das Befolgen Ihres Pseudocodes mir schlechte Dateideskriptoren geben.

    – mkab

    7. Dezember 2011 um 21:55 Uhr


  • Ich habe versucht, Ihren Code zu implementieren. Bitte überprüfen Sie meine bearbeitete Frage. Ich habe den Code hinzugefügt.

    – mkab

    7. Dezember 2011 um 22:54 Uhr


  • Ich habe es implementiert, um zu bestätigen, dass es funktioniert. Entschuldigung für die Index-Tippfehler. Auch hier ist es einfacher, immer nur zwei Pfeifensätze zu behalten und sie beim Schleifen zu drehen.

    – Christoph Neylan

    7. Dezember 2011 um 23:26 Uhr

  • Kein Problem :). Und nur um sicherzugehen, wäre es in meinem Fall, wenn ich Pipe-Dateideskriptoren deklarieren möchte int pipefds[2*numPipes] Rechts?

    – mkab

    7. Dezember 2011 um 23:42 Uhr


Hier ist der korrekte Funktionscode

void runPipedCommands(cmdLine* command, char* userInput) {
    int numPipes = countPipes(userInput);


    int status;
    int i = 0;
    pid_t pid;

    int pipefds[2*numPipes];

    for(i = 0; i < (numPipes); i++){
        if(pipe(pipefds + i*2) < 0) {
            perror("couldn't pipe");
            exit(EXIT_FAILURE);
        }
    }


    int j = 0;
    while(command) {
        pid = fork();
        if(pid == 0) {

            //if not last command
            if(command->next){
                if(dup2(pipefds[j + 1], 1) < 0){
                    perror("dup2");
                    exit(EXIT_FAILURE);
                }
            }

            //if not first command&& j!= 2*numPipes
            if(j != 0 ){
                if(dup2(pipefds[j-2], 0) < 0){
                    perror(" dup2");///j-2 0 j+1 1
                    exit(EXIT_FAILURE);

                }
            }


            for(i = 0; i < 2*numPipes; i++){
                    close(pipefds[i]);
            }

            if( execvp(*command->arguments, command->arguments) < 0 ){
                    perror(*command->arguments);
                    exit(EXIT_FAILURE);
            }
        } else if(pid < 0){
            perror("error");
            exit(EXIT_FAILURE);
        }

        command = command->next;
        j+=2;
    }
    /**Parent closes the pipes and wait for children*/

    for(i = 0; i < 2 * numPipes; i++){
        close(pipefds[i]);
    }

    for(i = 0; i < numPipes + 1; i++)
        wait(&status);
}

  • Fehlt in diesem Zustand etwas? if(j != 0 ){

    – Knü

    25. Januar 2014 um 21:55 Uhr

  • @Knu: Es könnte zu spät sein, aber nein, in diesem Zustand fehlt nichts.

    – mkab

    28. April 2014 um 17:07 Uhr

  • @mkab Bist du sicher? Sie haben j!= 2*numPipes im Kommentar über der Aussage.

    – wizzwizz4

    15. Januar 2017 um 14:09 Uhr

Der (gekürzte) relevante Code lautet:

    if(fork() == 0){
            // do child stuff here
            ....
    }
    else{
            // do parent stuff here
            if(command != NULL)
                command = command->next;

            j += 2;
            for(i = 0; i < (numPipes ); i++){
               close(pipefds[i]);
            }
           while(waitpid(0,0,0) < 0);
    }

Das bedeutet, dass der übergeordnete (kontrollierende) Prozess dies tut:

  • Gabel
  • schließen Sie alle Rohre
  • auf untergeordneten Prozess warten
  • nächste Schleife / Kind

Aber es sollte ungefähr so ​​sein:

  • Gabel
  • Gabel
  • Gabel
  • schließe alle Rohre (jetzt sollte alles gedoppt sein)
  • auf Kinder warten

  • Wenn ich Sie gut verstehe, sollte ich einen weiteren Fork erstellen, wenn dieser Fork dann 0 ist, überprüfe ich while(command != NULL). Dann behalte ich den gesamten Code, den ich oben geschrieben habe, im while-Befehl. Habe ich recht?

    – mkab

    5. Dezember 2011 um 20:39 Uhr

  • Der Code in meiner Antwort ist KEIN Vorschlag zur Lösung des Problems. Es ist eine Zusammenfassung IHRES Codes mit einem kleinen Schwerpunkt darauf, was tatsächlich passiert.

    – AH

    5. Dezember 2011 um 21:11 Uhr

  • Ja ich weiß. Es ist der gleiche Code wie bei mir. Ich habe nur versucht zu verstehen, was Sie vorgeschlagen haben. Bitte überprüfen Sie meine bearbeitete Frage. Ich habe etwas Code hinzugefügt.

    – mkab

    5. Dezember 2011 um 21:15 Uhr


Aufbauend auf der von Christopher Neylan erwähnten Idee, maximal zwei Pipes zu einem bestimmten Zeitpunkt zu verwenden, habe ich Pseudocode für n-Pipes zusammengestellt. args ist ein Array von Zeichenzeigern der Größe ‘args_size’, die eine globale Variable ist.

// MULTIPLE PIPES
// Test case:   char *args[] = {"ls", "-l", "|", "head", "|", "tail", "-4", 
0};// "|", "grep", "Txt", 0};   
enum fileEnd{READ, WRITE};

void multiple pipes( char** args){
pid_t cpid;
// declare pipes
int pipeA[2]
int pipeB[2]
// I have done getNumberofpipes
int numPipes = getNumberOfPipes;
int command_num = numPipes+1;
// holds sub array of args 
// which is a statement to execute
// for example: cmd = {"ls", "-l", NULL}
char** cmd 
// iterate over args
for(i = 0; i < args_size; i++){
  // 
  // strip subarray from main array
  //  cmd 1 | cmd 2 | cmd3 => cmd
  // cmd = {"ls", "-l", NULL}
  //Open/reopen one pipe

  //if i is even open pipeB
    if(i % 2)  pipe(pipeB);
  //if i is odd open pipeA
    else       pipe(pipeA);


  switch(cpid = fork(){
      case -1: error forking
      case 0: // child process
            childprocess(i);
      default: // parent process
           parentprocess(i, cpid);
  }
}
}
// parent pipes must be closed in parent
void parentprocess(int i, pid_t cpid){

   // if first command
   if(i == 0)  
        close(pipeB[WRITE]);

   // if last command close WRITE
   else if (i == numPipes){
       // if i is even close pipeB[WRITE]
       // if i is odd close pipeA[WRITE]
   }

   // otherwise if in middle close READ and WRITE 
   // for appropriate pipes
      // if i is even
      close(pipeA[READ])
      close(pipeB[WRITE])
      // if i is odd
      close(pipeB[READ])
      close(pipeA[WRITE])
   }

   int returnvalue, status;
   waitpid(cpid, returnvalue, status);
}
void childprocess(int i){

    // if in first command
    if(i == 0)
        dup2(pipeB[WRITE], STDOUT_FILENO);
    //if in last command change stdin for
    // the necessary pipe. Don't touch stdout - 
    // stdout goes to shell
    else if( numPipes == i){
        // if i is even
        dup2(pipeB[READ], STDIN_FILENO)
        //if i is odd
        dup2(pipeA[READ], STDIN_FILENO);        
    }
    // otherwise, we are in middle command where
    // both pipes are used.
    else{
       // if i is even
       dup2(pipeA[READ], STDIN_FILENO)
       dupe(pipeB[WRITE], STDOUT_FILENO)
       // if i is odd
       dup2(pipeB[READ], STDIN_FILENO)
       dup2(pipeA[WRITE], STDOUT_FILENO)
    }

    // execute command for this iteration
    // check for errors!!
    // The exec() functions only return if an error has occurred. The return value is -1, and errno is set to indicate the error.
    if(exec(cmd, cmd) < 0)
        printf("Oh dear, something went wrong with read()! %s\n", strerror(errno));
    }   
}

Benutzeravatar von eastriver lee
Ostfluss Lee

Sie brauchen nur zwei Rohre abwechselnd wie unten:

typedef int io[2];

extern int I; //piped command current index
extern int pipe_count; //count of '|'

#define CURRENT 0
#define PREVIOUS 1
#define READ 0
#define WRITE 1
#define is_last_command (I == pipe_count)

bool connect(io pipes[2])
{
    if (pipe_count)
    {
        if (is_last_command || I != 0)
            dup2(pipes[PREVIOUS][READ], STDIN_FILENO);
        if (I == 0 || !is_last_command)
            dup2(pipes[CURRENT][WRITE], STDOUT_FILENO);
    }
    return (true);
}

void close_(io pipes[2])
{
    if (pipe_count)
    {
        if (is_last_command || I != 0)
            close(pipes[PREVIOUS][READ]);
        if (I == 0 || !is_last_command)
            close(pipes[CURRENT][WRITE]);
    }
}

void alternate(int **pipes)
{
    int *pipe_current;

    pipe_current = pipes[CURRENT];
    pipes[CURRENT] = pipes[PREVIOUS];
    pipes[PREVIOUS] = pipe_current;
}

Beispielnutzung:

#define ERROR -1
#define CHILD 0

void execute(char **command)
{
    static io pipes[2];

    if (pipe_count && pipe(pipes[CURRENT]) == ERROR)
        exit_error("pipe");
    if (fork()==CHILD && connect(pipes))
    {
        execvp(command[0], command);
        _exit(EXIT_FAILURE);
    }
    while (wait(NULL) >= 0);
    close_(pipes);
    alternate((int **)pipes);
}

static void run(char ***commands)
{
    for (I = 0; commands[I]; I++)
        if (*commands[I])
            execute(commands[I]);
}

Ich lasse ein Verknüpfung zu einem voll funktionsfähigen Code für jemanden, der ihn braucht.

Kevs Benutzeravatar
Kev

Was Sie im Grunde tun möchten, ist eine rekursive Funktion, bei der das Kind den ersten Befehl ausführt und der Elternteil den zweiten ausführt, wenn keine anderen Befehle mehr vorhanden sind oder ruft die Funktion erneut auf.

1437380cookie-checkImplementierung mehrerer Pipes in C

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy