Может ли popen () создавать двунаправленные каналы, такие как pipe () + fork ()?

Я реализую трубопровод на моделируемой файловой системе на C ++ (в основном на C). Он должен запускать команды в хост-оболочке, но сам выполнять конвейер в моделируемой файловой системе.

Я мог бы достичь этого сpipe(), fork(), а такжеsystem() системные вызовы, но я бы предпочел использоватьpopen() (который обрабатывает создание канала, разветвление процесса и передачу команды в оболочку). Это может быть невозможно, потому что (я думаю) мне нужно иметь возможность писать из родительского процесса канала, читать в конце дочернего процесса, записывать выходные данные от дочернего процесса и, наконец, читать эти выходные данные из родительского процесса. Справочная страница дляpopen() в моей системе говорится, что двунаправленный канал возможен, но мой код должен работать в системе с более старой версией, поддерживающей только однонаправленные каналы.

С помощью отдельных вызовов выше, я могу открывать / закрывать каналы для достижения этой цели. Это возможно сpopen()?

Для тривиального примера, чтобы запуститьls -l | grep .txt | grep cmds Мне необходимо:

Открыть трубу и запустить процессls -l на хосте; читать его вывод обратноТруба на выходеls -l вернуться к моему симуляторуОткрыть трубу и запустить процессgrep .txt на хосте по каналу выводаls -lПередайте вывод этого обратно на симулятор (застрял здесь)Открыть трубу и запустить процессgrep cmds на хосте по каналу выводаgrep .txtПередайте вывод этого обратно на симулятор и распечатайте его

человек попен

Из Mac OS X:

popen() Функция «открывает» процесс, создавая двунаправленный канал, разветвляясь и вызывая оболочку. Любые потоки, открытые предыдущимpopen() вызовы в родительском процессе закрываются в новом дочернем процессе. Исторически,popen() был реализован с однонаправленной трубой; следовательно, многие реализацииpopen() разрешить только аргумент mode указывать чтение или запись, но не то и другое одновременно Так какpopen() теперь реализован с использованием двунаправленного канала, аргумент mode может запрашивать двунаправленный поток данных. Аргумент mode - это указатель на завершающуюся нулем строку, которая должна быть «r» для чтения, «w» для записи или «r +» для чтения и записи.

 R..07 окт. 2010 г., 21:20
Не обращайте внимания на это плохое руководство, из-за которого кажется, что однонаправленность каналов является устаревшим поведением. Поведение вашей реализации нестандартно и вряд ли когда-либо будет поддерживаться в других средах. Просто настройте трубы и вилку и исполняйте сами, и все будут счастливы.
 sager8914 мар. 2014 г., 16:14
Я только что сделалчеловек попен на Ubuntu 13.04 и в нем говорится:Since a pipe is by definition unidirectional, the type argument may specify only reading or writing, not both; the resulting stream is correspondingly read-only or write-only. Я удивлен, что это "пожилой" поп ...
 Matt Joiner03 мая 2011 г., 03:14
Вся эта тема сделана из победы, все ответы и вопросы заслуживают +1.

Ответы на вопрос(5)

POSIX предусматривает, чтоpopen() Вызов не предназначен для обеспечения двунаправленной связи:

Аргумент mode для popen () - это строка, которая определяет режим ввода-вывода:

Если режим равен r, когда дочерний процесс запускается, его файловый дескриптор STDOUT_FILENO должен быть записываемым концом канала, а файловый дескриптор fileno (stream) в вызывающем процессе, где stream - указатель потока, возвращаемый popen (), должен быть читаемым концом трубы.Если режим равен w, то при запуске дочернего процесса его файловый дескриптор STDIN_FILENO должен быть читаемым концом канала, а файловый дескриптор fileno (stream) в вызывающем процессе, где stream - указатель потока, возвращаемый popen (), должен быть записываемым концом трубы.Если для режима задано любое другое значение, результат не указан.

Любой переносимый код не будет делать никаких предположений. BSDpopen() похоже на то, что описывает ваш вопрос.

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

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

typedef void pfunc_t (int rfd, int wfd);

pid_t pcreate(int fds[2], pfunc_t pfunc) {
    /* Spawn a process from pfunc, returning it's pid. The fds array passed will
     * be filled with two descriptors: fds[0] will read from the child process,
     * and fds[1] will write to it.
     * Similarly, the child process will receive a reading/writing fd set (in
     * that same order) as arguments.
    */
    pid_t pid;
    int pipes[4];

    /* Warning: I'm not handling possible errors in pipe/fork */

    pipe(&pipes[0]); /* Parent read/child write pipe */
    pipe(&pipes[2]); /* Child read/parent write pipe */

    if ((pid = fork()) > 0) {
        /* Parent process */
        fds[0] = pipes[0];
        fds[1] = pipes[3];

        close(pipes[1]);
        close(pipes[2]);

        return pid;

    } else {
        close(pipes[0]);
        close(pipes[3]);

        pfunc(pipes[2], pipes[1]);

        exit(0);
    }

    return -1; /* ? */
}

Вы можете добавить туда любую необходимую вам функциональность.

 Martin Meeser20 янв. 2016 г., 17:58
Это чудесно! Спасибо БОЛЬШОЙ СТИЛЬ!

В одном изnetresolve бэкэнды я говорю со сценарием и, следовательно, мне нужно написать егоstdin и читать из егоstdout, Следующая функция выполняет команду с перенаправлением stdin и stdout в канал. Вы можете использовать его и адаптировать по своему вкусу.

static bool
start_subprocess(char *const command[], int *pid, int *infd, int *outfd)
{
    int p1[2], p2[2];

    if (!pid || !infd || !outfd)
        return false;

    if (pipe(p1) == -1)
        goto err_pipe1;
    if (pipe(p2) == -1)
        goto err_pipe2;
    if ((*pid = fork()) == -1)
        goto err_fork;

    if (*pid) {
        /* Parent process. */
        *infd = p1[1];
        *outfd = p2[0];
        close(p1[0]);
        close(p2[1]);
        return true;
    } else {
        /* Child process. */
        dup2(p1[0], 0);
        dup2(p2[1], 1);
        close(p1[0]);
        close(p1[1]);
        close(p2[0]);
        close(p2[1]);
        execvp(*command, command);
        /* Error occured. */
        fprintf(stderr, "error running %s: %s", *command, strerror(errno));
        abort();
    }

err_fork:
    close(p2[1]);
    close(p2[0]);
err_pipe2:
    close(p1[1]);
    close(p1[0]);
err_pipe1:
    return false;
}

https://github.com/crossdistro/netresolve/blob/master/backends/exec.c#L46

(Я использовал тот же код водновременное чтение и запись)

Решение Вопроса

Вы, кажется, ответили на свой вопрос. Если ваш код должен работать на старой системе, которая не поддерживаетpopen открывая двунаправленные трубы, тогда вы не сможете использоватьpopen (по крайней мере, не тот, который поставляется).

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

Если вам нужно поддерживать достаточно старые системы,pipe поддерживает только однонаправленные каналы, тогда вы в значительной степени застряли с использованиемpipe, fork, dup2и т. д. самостоятельно. Я бы, наверное, все еще обернул это в функцию, которая работаетпочти как современная версияpopen, но вместо возврата одного дескриптора файла заполняет небольшую структуру двумя дескрипторами файла, один для дочернего элемента.stdin, другой для ребенкаstdout.

 Cardin23 нояб. 2013 г., 10:54
+1 для popen для одного направления + pipe () для другого. Спасает меня от переписывания большинства утверждений cout, и вместо этого нужно только изменить cin.
 Taylor Edmiston23 окт. 2010 г., 02:58
Я думаю, что вы помогли мне понять, что мой вопрос был действительно «Двунаправленная способность« распространена »?», И, похоже, это не так. Спасибо за отличный ответ.

Не нужно создавать два канала и тратить файловый дескриптор в каждом процессе. Просто используйте сокет вместо этого.https://stackoverflow.com/a/25177958/894520

 Pavel Šimerda28 февр. 2017 г., 13:04
Я также не уверен, что сокет требуется в этом случае. Но если это действительно лучше, чем трубы, я хотел бы знать, как и почему.
 Martin Meeser20 янв. 2016 г., 17:55
Не могли бы вы добавить более подробную информацию о том, почему файловый дескриптор для каждого процесса является «ненужным»? Я имею в виду, что это стоит? Что дешевле с доменным сокетом?

Ваш ответ на вопрос