мульти трубы в C - PullRequest
       52

мульти трубы в C

0 голосов
/ 07 августа 2011

Я пытаюсь реализовать несколько каналов в 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 несколько раз и для каждого дескриптора файла, считываемого из дескриптора файла результата, - похоже, он влияет на дескриптор копируемого файла?

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

Ответы [ 2 ]

2 голосов
/ 07 августа 2011

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

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

0 голосов
/ 07 августа 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.

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

...