Реализация нескольких труб в C

Я пытаюсь реализовать несколько труб в своей оболочке в 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);

    }

}

Ответ 1

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

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

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

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

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

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

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

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

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

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

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

Ответ 2

Здесь правильный код функционирования

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

Ответ 3

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

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

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

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

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

  • вилка
  • вилка
  • вилка
  • закрыть все трубы (все должно было быть обмануто сейчас)
  • wait for childs

Ответ 4

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

Ответ 5

Основываясь на идее использования максимум двух труб в определенное время, упомянутых Кристофером Неяном, я собрал псевдокод для 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));
    }   
}