web-dev-qa-db-fra.com

Implantation de multiples tuyaux en C

J'essaie d'implémenter plusieurs canaux dans mon shell en C. J'ai trouvé un tutoriel sur ce website et la fonction que j'ai créée est basée sur cet exemple. Voici la fonction

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);
        }
    }

}

Après l’exécution et la saisie d’une commande comme par exemple ls | grep bin, le shell s’y bloque et ne produit aucun résultat. Je me suis assuré de fermer tous les tuyaux. Mais ça reste juste là. Je pensais que c’était la waitpid qui était le problème. J'ai enlevé la waitpid et après exécution je n'obtiens aucun résultat. Qu'ai-je fait de mal? Merci.

Code ajouté:

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);

    }

}
12
mkab

Je pense que le problème ici est que votre attente et votre fermeture dans la même boucle qui crée des enfants. À la première itération, l’enfant exécutera (ce qui détruira le programme enfant et le remplacera avec votre première commande), puis le parent fermera tous ses descripteurs de fichier et attendra que l’enfant soit terminé avant qu’il ne crée le prochain enfant. . À ce stade, étant donné que le parent a fermé tous ses canaux, tout enfant supplémentaire n'aura plus rien pour écrire ou lire. Puisque vous ne vérifiez pas le succès de vos appels dup2, cela ne se remarque pas.

Si vous souhaitez conserver la même structure de boucle, vous devez vous assurer que le parent ne ferme que les descripteurs de fichier déjà utilisés, mais laisse ceux qui ne sont pas seuls. Ensuite, une fois que tous les enfants ont été créés, votre parent peut attendre.

EDIT: J'ai mélangé le parent/enfant dans ma réponse, mais le raisonnement reste le même: le processus qui va continuer à brancher ferme à nouveau toutes ses copies des tuyaux, donc tout processus après le premier fork n'aura pas de descripteur de fichier valide pour lire/écrire.

pseudo-code, utilisant un tableau de pipes créé en amont:

/* 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] );
}

Dans ce code, le processus parent d'origine crée un enfant pour chaque commande et survit donc à l'épreuve entière. Les enfants vérifient s'ils doivent obtenir leur entrée de la commande précédente et s'ils doivent envoyer leur sortie à la commande suivante. Ensuite, ils ferment toutes leurs copies des descripteurs de fichiers de pipe, puis exec. Le parent ne fait rien que bifurquer jusqu'à ce qu'il ait créé un enfant pour chaque commande. Il ferme ensuite toutes les copies des descripteurs et peut attendre.

Tout d'abord, créer tous les canaux dont vous avez besoin, puis les gérer en boucle, est délicat et nécessite quelques calculs arithmétiques. Le but, cependant, ressemble à ceci:

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

En réalisant que, à un moment donné, vous n'avez besoin que de deux ensembles de tuyaux (le tuyau pour la commande précédente et le tuyau pour la commande suivante), cela simplifiera votre code et le rendra un peu plus robuste. Ephemient donne un pseudo-code pour ceci ici . Son code est plus propre, car le parent et l'enfant n'ont pas besoin de boucler inutilement pour fermer les descripteurs de fichiers inutiles et parce que le parent peut facilement fermer ses copies des descripteurs de fichiers immédiatement après le fork.

Remarque: vous devez toujours vérifier les valeurs de retour de pipe, dup2, fork et exec.

EDIT 2 : faute de frappe en pseudo-code. OP: num-pipes serait le nombre de pipes. Par exemple, "ls | grep foo | sort -r" aurait 2 pipes.

14
Christopher Neylan

Voici le code de fonctionnement correct

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);
}
7
mkab

Le code pertinent (abrégé) est:

    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);
    }

Ce qui signifie que le processus parent (de contrôle) effectue ceci:

  • fork
  • fermez tous les tuyaux
  • attendre le processus fils
  • prochaine boucle/enfant

Mais cela devrait être quelque chose comme ça:

  • fork
  • fork
  • fork
  • fermez tous les tuyaux (tout aurait dû être dupé maintenant)
  • attendre les enfants
2
A.H.

Partant de l’idée d’utiliser un maximum de deux pipes à un moment donné, évoquée par Christopher Neylan, j’ai construit le pseudocode pour les n-pipes. args est un tableau de pointeurs de caractère de taille 'args_size' qui est une variable globale.

// 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));
    }   
}
0
Elizabeth Bradley

Fondamentalement, ce que vous voulez faire est une fonction récursive dans laquelle l'enfant exécute la première commande et le parent exécute la deuxième si aucune autre commande n'est laissée ou appelle à nouveau la fonction.

0
Mathieu_Du