Как правильно использовать канал для передачи данных из дочернего процесса в родительский?

Я пытаюсь создать функцию, которая возвращает true, если execvp выполнена успешно, и false, если нет. Первоначально я не использовал канал, и проблема заключалась в том, что при сбое execvp я получаю 2 возврата, один false и один true (от родителя). Теперь, когда я работаю по конвейеру, я никогда не получаю false, когда execvp терпит неудачу.

Я знаю, что по этой теме есть много связанных вопросов и ответов, но я не могу сузить круг моей конкретной ошибки. Я хочу, чтобы мои переменные return_type_child, return_type_parent и this->return_type содержали одно и то же значение. Я ожидал, что в дочернем процессе execvp завершится ошибкой, поэтому будут выполняться следующие строки. В результате я подумал, что все 3 упомянутые переменные будут ложными, но вместо этого, когда я распечатываю значение в this->return_type, отображается 1.

bool Command::execute() {
    this->fork_helper();
    return return_type;
}

void Command::fork_helper() {
    bool return_type_child = true;
    int fd[2];
    pipe(fd);
    pid_t child;
    char *const argv[] = {"zf","-la", nullptr};
    child = fork();
    if (child > 0) {
        wait(NULL);
        close(0);
        close(fd[1]);
        dup(fd[0]);
        bool return_type_parent = read(fd[0], &return_type_child, sizeof(return_
        this->return_type = return_type_parent;
    }
    else if (child == 0) {
        close(fd[0]);
        close(1);
        dup(fd[1]);
        execvp(argv[0], argv);
        this->return_type = false;
        return_type_child = false;
        write(1,&return_type_child,sizeof(return_type_child));
    }
    return;
}

Я также пытался поместить оператор cout после execvp(argv[0], argv), который так и не запустился. Любая помощь приветствуется!


person Sarah Allec    schedule 17.02.2019    source источник
comment
Вы пропускаете fd[0] в родителя. Кроме того, если execvp завершается успешно, кто или что записывает возвращаемую информацию в родителя?   -  person David Schwartz    schedule 17.02.2019
comment
вы никогда не запустите кусок кода, который вы написали после execvp. Семейство функций exec() заменяет текущий образ процесса новым образом процесса.   -  person Mellester    schedule 17.02.2019
comment
Ладно, думаю, я понял. у вас есть какие-либо другие рекомендации для этого? главное, что мне нужно, это вернуть логическое значение в зависимости от того, сбой или успех execvp   -  person Sarah Allec    schedule 17.02.2019
comment
fork возвращает pid дочерних процессов. вы можете дождаться, а затем запросить у родителя, вышел ли дочерний элемент и каково было его возвращаемое значение. linux.die.net/man/2/wait   -  person Mellester    schedule 17.02.2019


Ответы (1)


Судя по коду, это проблема XY (изменить: переместить это раздел впереди из-за комментария, подтверждающего это). Если цель состоит в том, чтобы получить статус выхода дочернего элемента, то для этого есть значение, которое wait возвращает, и каналы не требуются:

int stat;
wait(&stat);

Прочитайте руководство wait, чтобы понять, как его читать. Значение stat можно проверить следующим образом:

  • WEXITSTATUS(stat) — если WIFEXITED(stat) != 0, то это младшие 8 бит дочернего вызова exit(N) или возвращаемое значение из main. Он может работать корректно без проверки WIFEXITED, но в стандарте это не указано.
  • WTERMSIG(stat) — если WIFSIGNALED(stat) != 0, то это номер сигнала, вызвавший завершение процесса (например, 11 — ошибка сегментации). Он может работать корректно без проверки WIFSIGNALED, но в стандарте это не указано.

В коде есть несколько ошибок. Смотрите добавленные комментарии:

void Command::fork_helper() {
    // File descriptors here: 0=stdin, 1=stdout, 2=stderr 
    //(and 3..N opened in the program, could also be none).
    bool return_type_child = true;
    int fd[2];
    pipe(fd);
    // File descriptors here: 0=stdin, 1=stdout, 2=stderr 
    //(and 3..N opened in the program, could also be none).
    // N+1=fd[0] data exhaust of the pipe
    // N+2=fd[1] data intake of the pipe
    pid_t child;
    char *const argv[] = {"zf","-la", nullptr};
    child = fork();
    if (child > 0) {
        // This code is executed in the parent.
        wait(NULL); // wait for the child to complete.

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

        close(0);
        close(fd[1]);
        dup(fd[0]);
        // File descriptors here: 0=new stdin=data exhaust of the pipe
        // 1=stdout, 2=stderr 
        // (and 3..N opened in the program, could also be none).
        // N+1=fd[0] data exhaust of the pipe (stdin is now a duplicate)

Это проблематично, поскольку:

  1. код только что потерял исходный стандартный ввод.
  2. Труба никогда не закрывается. Вы должны закрыть fd[0] явно, не закрывать (0) и не дублировать fd[0].
  3. Рекомендуется избегать дублирования дескрипторов, за исключением дублирования stderr stdout.

.

        bool return_type_parent = read(fd[0], &return_type_child, sizeof(return_
        this->return_type = return_type_parent;

    }
    else if (child == 0) {
        // this code runs in the child.
        close(fd[0]);
        close(1);
        dup(fd[1]);
        // File descriptors here: 0=stdin, 1=new stdout=pipe intake, 2=stderr 
        //(and 3..N opened in the program, could also be none).
        // N+2=fd[1] pipe intake (new stdout is a duplicate)

Это проблематично, так как в канал поступает два дублирующих данных. В данном случае это не критично, так как они оба закрываются автоматически по завершении процесса, но это плохая практика. Это плохая практика, так как только закрытие всех воздухозаборников сигнализирует END-OF-FILE выхлопу. Закрытие одного входа, но не другого, не сигнализирует КОНЕЦ ФАЙЛА. Опять же, в вашем случае это не вызывает проблем, так как выход ребенка закрывает все входы.

        execvp(argv[0], argv);

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

        this->return_type = false;
        return_type_child = false;
        write(1,&return_type_child,sizeof(return_type_child));
    }
    return;
 }
person Michael Veksler    schedule 17.02.2019
comment
Спасибо за ваше подробное объяснение. Я вынул трубу и просто пытаюсь работать с ожиданием. Теперь моя проблема в том, что я всегда получаю return true (waitpid(child, &status, 0), а затем проверяю, не равно ли WIFEXITED(status) нулю. Я предполагаю, что проблема именно в этом. - person Sarah Allec; 18.02.2019
comment
@SarahAllec waitpid не возвращает логическое значение. В вашем случае он возвращает только идентификатор процесса дочернего элемента. Статус можно извлечь только из переменной status - person Michael Veksler; 18.02.2019