Реализация нескольких труб в C - PullRequest
12 голосов
/ 05 декабря 2011

Я пытаюсь реализовать несколько каналов в моей оболочке на C. Я нашел учебник на этом сайте , и функция, которую я сделал, основана на этом примере.Вот функция

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

}

После ее выполнения и ввода команды, такой как, например, ls | grep bin, оболочка просто зависает и не выдает никакого результата.Я удостоверился, что закрыл все трубы.Но это просто висит там.Я думал, что это была проблема waitpid.Я удалил waitpid и после выполнения не получил результатов.Что я сделал не так?Спасибо.

Добавлен код:

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

    }

}

Ответы [ 5 ]

14 голосов
/ 07 декабря 2011

Я считаю, что проблема в том, что вы ждете и замыкаете внутри того же цикла, который создает детей. На первой итерации дочерний элемент выполнит exec (который уничтожит дочернюю программу, перезаписав ее первой командой), а затем родительский файл закроет все свои файловые дескрипторы и ожидает завершения дочернего процесса, прежде чем он перейдет к созданию следующего дочернего элемента. , В этот момент, поскольку родитель закрыл все свои каналы, дальнейшим дочерним элементам нечего будет писать или читать. Поскольку вы не проверяете успешность ваших вызовов dup2, это остается незамеченным.

Если вы хотите сохранить ту же структуру цикла, вам нужно убедиться, что родительский элемент закрывает только те файловые дескрипторы, которые уже были использованы, но оставляет те, которые не были одинокими. Затем, после того как все дети созданы, ваш родитель может подождать.

РЕДАКТИРОВАТЬ : Я перепутал родителя / потомка в своем ответе, но рассуждения все еще верны: процесс, который переходит к форку снова, закрывает все его копии каналов, поэтому любой процесс после первая ветвь не будет иметь допустимых файловых дескрипторов для чтения / записи.

псевдокод с использованием массива каналов, созданных заранее:

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

В этом коде исходный родительский процесс создает дочерний элемент для каждой команды и, следовательно, выдерживает все испытания. Дети проверяют, должны ли они получать свои данные от предыдущей команды и должны ли они отправлять свои выходные данные следующей команде. Затем они закрывают все свои копии дескрипторов файла канала и затем исполняются. Родитель ничего не делает, кроме fork, пока не создаст дочерний элемент для каждой команды. Затем он закрывает все свои копии дескрипторов и может ждать.

Создание всех необходимых вам каналов, а затем управление ими в цикле довольно сложно и требует некоторой арифметики с массивами. Цель, однако, выглядит так:

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

Понимая, что в любой момент вам понадобятся только два набора каналов (канал для предыдущей команды и канал для следующей команды), что упростит ваш код и сделает его немного более устойчивым. Ephemient дает псевдокод для этого здесь . Его код чище, потому что родитель и потомок не должны делать ненужных циклов, чтобы закрыть ненужные файловые дескрипторы, и потому что родитель может легко закрыть свои копии файловых дескрипторов сразу после разветвления.

В качестве примечания: вы всегда должны проверять возвращаемые значения pipe, dup2, fork и exec.

РЕДАКТИРОВАТЬ 2 : опечатка в псевдокоде. ОП: количество труб будет числом труб. Например, "ls | grep foo | sort -r" будет иметь 2 канала.

7 голосов
/ 09 декабря 2011

Вот правильный код функционирования

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);
}
2 голосов
/ 05 декабря 2011

(укороченный) соответствующий код:

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

Что означает, что родительский (управляющий) процесс делает это:

  • fork
  • закрыть все трубы
  • ожидание дочернего процесса
  • следующий цикл / дочерний процесс

Но это должно быть примерно так:

  • fork
  • fork
  • fork
  • закрыть все трубы (все должно было быть дублировано)
  • ждать потомков
0 голосов
/ 15 февраля 2018

Опираясь на идею использования максимум двух каналов в данный момент времени, упомянутую Кристофером Нейланом, я собрал псевдокод для n-каналов.args - это массив символьных указателей размером 'args_size', который является глобальной переменной.

// 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 голосов
/ 05 декабря 2011

По сути, вы хотите сделать рекурсивную функцию, когда дочерний элемент выполняет первую команду, а родительский выполняет вторую, если не осталось других команд или вызывает функцию снова.

...