2010-10-07 3 views
18

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

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

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

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

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

человек POPEN

С Mac OS X:

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

+3

Игнорируйте это плохое руководство, которое делает его, кажется, как однонаправленность труб является наследством поведение. Поведение вашей реализации нестандартно и вряд ли когда-либо будет поддерживаться в других средах. Просто установите трубы и вилку и выполните сами, и все будут счастливы. –

+2

Вся эта тема сделана из победы, все ответы и вопрос заслуживают +1. –

+0

Я только что сделал ** man popen ** на Ubuntu 13.04, и он гласит: 'Поскольку канал по определению однонаправленный, аргумент типа может указывать только чтение или запись, а не оба; результирующий поток соответственно только для чтения или только для записи. «Я удивлен, что это« более старый »popen ... – sager89

ответ

10

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

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

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

+0

Я думаю, вы помогли мне понять, что мой вопрос действительно:« Является ли двунаправленная возможность », общий «?», и, похоже, это не так. Спасибо за отличный ответ. –

+0

+1 для всплывающего окна для одного направления + труба() для другого. Сохраняет меня от перезаписи большинства заявлений cout, и вместо этого нужно только изменить cin. – Cardin

8

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

Режим аргумент POPEN() является строкой, которая определяет режим ввода/вывода:

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

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

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

19

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

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; /* ? */ 
} 

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

+0

Это замечательно! Спасибо BIG STYLE! –

1

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

+1

Не могли бы вы добавить дополнительные сведения, почему filedescriptor для каждого процесса будет «отходами»? Я имею в виду, что это стоит? Что дешевле с доменным сокетом? –

+0

Я также не уверен, что в этом случае требуется сокет. Но если это действительно лучше, чем трубы, я хотел бы знать, как и почему. –

0

В одном из 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 simultaneous read and write)

Смежные вопросы