несколько труб в C

Я пытаюсь реализовать несколько каналов в C, решение должно быть для:

cmd1 | cmd2 | cmd3

и для:

        |--- cmd2

cmd1    |--- cmd3

        |--- cmd4
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <signal.h>

int main(int argc, char *argv[]) {

char* args1[] = { "ls", NULL, NULL };
char* args2[] = { "ls", "-l", NULL };
char* args3[] = { "sort", NULL, NULL };
char* args4[] = { "wc", "-l", NULL };

int rc1 = execute_cmd(args1, 0);
//printf("rc1 = %d\n", rc1);

int rc2 = execute_cmd(args2, rc1);
//printf("rc2 = %d\n", rc2);

int rc3 = execute_cmd(args3, rc1);
//printf("rc3 = %d\n", rc3);

int rc4 = execute_cmd(args4, rc1);
//printf("rc4 = %d\n", rc4);

int buffer[1024];
int len = 0;

if (rc2) {
    while ((len = read(rc2, buffer, sizeof(buffer))) > 0) {
        write(STDERR_FILENO, "rc2\n", 4);
        write(STDERR_FILENO, &buffer, len);
    }
} else {
    printf(stderr, "ERROR\n");
}

if (rc3) {
    while ((len = read(rc3, buffer, sizeof(buffer))) > 0) {
        write(STDERR_FILENO, "rc3\n", 4);
        write(STDERR_FILENO, &buffer, len);
    }
} else {
    printf(stderr, "ERROR\n");
}

if (rc4) {
    while ((len = read(rc4, buffer, sizeof(buffer))) > 0) {
        write(STDERR_FILENO, "rc4\n", 4);
        write(STDERR_FILENO, &buffer, len);
    }
} else {
    printf(stderr, "ERROR\n");
}

return 0;
}

int execute_cmd(char** args, int fd_in) {

int pipefd[2];
pipe(pipefd);

if (fork() == 0) {
    close(pipefd[0]);

    dup2(pipefd[1], STDOUT_FILENO);
    dup2(pipefd[1], STDERR_FILENO);

    close(pipefd[1]);

    if (fd_in) {
        dup2(fd_in, 0);
    }

    execvp(*args, args);
    printf("failed to execute %s %s", *args, *args[0]);
} else {
    close(pipefd[1]);

    return pipefd[0];

}
}

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

Если это работа, как я упоминаю, по дизайну, какой системный вызов мне нужно использовать для обоих?


person Marry    schedule 07.08.2011    source источник
comment
На самом деле это не вопрос C, а вопрос Unix.   -  person hmakholm left over Monica    schedule 07.08.2011


Ответы (2)


Да, dup и dup2 создают полностью эквивалентные дескрипторы одного и того же канала. Если несколько процессов (или потоков) одновременно пытаются читать из канала, используя дублированные/разветвленные дескрипторы, «случайный» из них получит данные первым, но каждый байт, записанный в канал, доставляется только один раз.

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

person hmakholm left over Monica    schedule 07.08.2011

Наличие нескольких дескрипторов/ссылок на один и тот же канал вызовет много проблем с синхронизацией и т.д.

Например, если есть 2 дочерних процесса, один из которых отправляет "Hello\n", затем "World\n", а другой отправляет "Foo\n", затем "Bar\n"; то вы можете получить "Hello\n World\n Foo\n Bar\n" или "Hello\n Foo\n World\n Bar" или "Foo\n Hello\n Bar\n World" и т. д. вывод оказывается неупорядоченным (что было бы крайне запутанным).

Решение состоит в том, чтобы использовать разные трубы.

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

Основная программа также может добавлять дополнительную информацию и выполнять форматирование, чтобы было понятнее, что происходит. Для приведенного выше примера вы можете получить:

Process A (exit status = 0, OK):
    Hello
    World
Process B (exit status = 1, Failed):
    Foo
    Bar

Вместо того, чтобы просто:

 Hello
 World
 Foo
 Bar

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

Другая альтернатива состоит в том, чтобы иметь «выбранный в данный момент дочерний элемент», что может стать намного более сложным (особенно если есть конечный пользователь, которому необходимо иметь возможность видеть вывод выбранного дочернего элемента, так как это означало бы реализацию некоторого способа переключения между «отображаемыми в данный момент» дочерними элементами — например, когда пользователь выбирает другого дочернего элемента, очищает экран и отображает невыполненную работу этого дочернего элемента).

person Brendan    schedule 07.08.2011