В C, почему моя программа зависает в wait () при попытке передать несколько команд? - PullRequest
0 голосов
/ 13 июня 2019

Я пишу простую программу для передачи между тремя командами. Для каждой команды я разветвляю дочерний элемент и вызываю в нем execvp, в то время как родитель ожидает завершения execvp (обратите внимание, что, поскольку я разветвляюсь три раза, я трижды вызываю wait (NULL))

Я создал две трубы, pipe_A и pipe_Z. Поток что-то вроде:

Команда 1 записывает свой вывод в pipe_A, команда 2 читает входные данные из pipe_A, затем записывает выходные данные в pipe_Z, затем команда 3 читает входные данные из pipe_Z, а затем записывает в стандартный вывод.

Проблема в том, что программа застревает во втором ожидании (NULL). В моем коде это строка сразу после 'printf ("достигла \ n");'. Программа не завершается и застревает, пока я не отправлю прерывание.

Если я заменю все три команды на одну и ту же команду "echo hello", это ничего не даст, но программа завершится, по крайней мере.

Я написал программу меньшего размера (с одинаковой структурой), в которой всего две команды ("echo hello" и "wc"), и она также зависает только при первом ожидании (NULL). Тем не менее, я заметил, что если я вызываю execvp для второй команды в основной программе, вывод выводится, как и ожидалось. Однако программа также немедленно завершает работу (из-за execvp), поэтому я не хочу этого делать.

Я подозреваю, что совершаю очень очевидную ошибку из-за отсутствия понимания, как работают каналы, или как работает вызов функции ожидания (NULL).

int main () {

//### COMMANDS AS STRING ARRAYS ###

    char * cmds1[3] = {"echo","hello", NULL};
    char * cmds2[3] = {"cat","-",NULL};
    char * cmds3[3] = {"wc", NULL};

//### CREATING PIPES ###

    int pipe_A[2];
    int pipe_Z[2];

    pipe(pipe_A);
    pipe(pipe_Z);

//### FIRST FORK FOR FIRST EXECVP ###

    int pid = fork();

    //Child process
    if(pid==0){

        close(pipe_A[0]);
        dup2(pipe_A[1], STDOUT_FILENO);

        execvp(cmds1[0],cmds1);
        exit(1);

    }

    //Parent process
    else if(pid >0){
        wait(NULL);
    }

//### SECOND FORK FOR SECOND EXECVP ###

    int pid2 = fork();

    //Child process
    if(pid2==0){

        close(pipe_A[1]);
        dup2(pipe_A[0],STDIN_FILENO);

        close(pipe_Z[0]);
        dup2(pipe_Z[1], STDOUT_FILENO); 

        execvp(cmds2[0],cmds2);
        exit(1);

    }

    //Parent process
    else if(pid2>0){

        printf("reached\n");
        wait(NULL);

    }

//### THIRD FORK FOR THIRD EXECVP ###

    int pid3 = fork();

    //Child process
    if(pid3==0){

        close(pipe_Z[1]);
        dup2(pipe_Z[0],STDIN_FILENO);

        execvp(cmds3[0],cmds3);
        exit(1);

    }

    //Parent process
    else if(pid2 >0){

        wait(NULL);

    }


//### END OF PROGRAM ###

    return 0;

}

Ожидаемое значение «1 1 6», но программа не завершается.

Ответы [ 2 ]

3 голосов
/ 13 июня 2019

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

0 голосов
/ 14 июня 2019

вы говорите:

... пока родитель ожидает завершения execvp

и wait(2) никогда не ожидают завершения системного вызова execvp(2), но завершают всю программу, запущенную с помощью execvp(2).

Кстати, execvp(2) не возвращается в случае отсутствия ошибки. Хорошо бы показать в stderr, почему это не удалось, вместо того, чтобы сделать exit(1), потому что код выхода редко проверяется пользователем, и вы не будете знать, что произошло. Что-то вроде

execvp(...);
fprintf(stderr, "EXECVP: %s: %s\n", cmds?[0], strerror(errno));
exit(1);

было бы намного эффективнее. Он просто запускает другую программу в текущем процессе, дочерний элемент, который вы fork() редактировали от родительского.

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

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

Лучше дождаться их всех в конце ... как вы говорите, вы сделали три успешных fork() с, поэтому вам нужно сделать три wait() с, чтобы правильно завершить , В этом случае

for (i = 0; i < 3; i++) wait(NULL);

правильно (но в конце). (даже если какой-либо форк не был успешным, ожидание обнаружит это и завершится ошибкой, поэтому вам не нужно проверять ошибки)

...