popen()可以像pipe()+ fork()那样制作双向管道吗?
我正在用C ++(主要是C)在模拟文件系统上实现管道。 它需要在主机shell中运行命令,但在模拟文件系统上执行管道本身。
我可以通过pipe()
, fork()
和system()
系统调用实现这一点,但我更喜欢使用popen()
(它处理创建管道,分叉进程,并将命令传递给shell) 。 这可能是不可能的,因为(我认为)我需要能够从管道的父进程写入,在子进程端读取,从子进程写回输出,最后从父进程读取该输出。 我的系统上popen()
的手册页说可以使用双向管道,但是我的代码需要在一个只支持单向管道的旧版本的系统上运行。
通过上面的单独调用,我可以打开/关闭管道来实现这一目标。 这可能与popen()
?
对于一个简单的例子,运行ls -l | grep .txt | grep cmds
ls -l | grep .txt | grep cmds
ls -l | grep .txt | grep cmds
我需要:
- 打开一个管道和进程以在主机上运行
ls -l
; 读回它的输出 - 将
ls -l
的输出传回我的模拟器 - 打开一个管道和进程,在
ls -l
的管道输出上的主机上运行grep .txt
- 将此输出传回模拟器(卡在此处)
- 打开一个管道和进程,在
grep .txt
的管道输出上的主机上运行grep cmds
- 将其输出传回模拟器并打印出来
男人popen
从Mac OS X:
popen()
函数通过创建双向管道,分叉和调用shell来“打开”一个进程。 由父进程中先前的popen()
调用打开的任何流都将在新的子进程中关闭。 从历史上看,popen()
是用单向管道实现的; 因此,popen()
许多实现只允许mode参数指定读取或写入,而不是两者。 由于popen()
现在使用双向管道实现,因此mode参数可以请求双向数据流。 mode参数是一个指向以null结尾的字符串的指针,该字符串必须为’r’表示读取,’w’表示写入,或’r +’表示读写。
你似乎回答了自己的问题。 如果您的代码需要在不支持popen
打开双向管道的旧系统上工作,那么您将无法使用popen
(至少不能使用提供的popen
)。
真正的问题是有关旧系统的确切function。 特别是,他们的pipe
支持创建双向管道? 如果他们有一个可以创建双向管道的管道,但是popen
却没有,那么我会编写代码的主流来使用带有双向管道的popen
,并提供可以使用双向管道的popen
实现在需要的地方编译。
如果你需要支持足够大的系统, pipe
只支持单向管道,那么你自己就很难使用pipe
, fork
, dup2
等。 我可能仍然将它包装在一个几乎像现代版本的popen
的函数中,但不是返回一个文件句柄,而是填充一个带有两个文件句柄的小结构,一个用于孩子的stdin
,另一个用于孩子的stdin
。 stdout
。
我建议您编写自己的function来为您进行管道/分叉/系统管理。 您可以让函数生成一个进程并返回读/写文件描述符,如…
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; /* ? */ }
您可以在其中添加所需的任何function。
POSIX规定popen()
调用不是为了提供双向通信而设计的:
popen()的mode参数是一个指定I / O模式的字符串:
- 如果mode是r,当子进程启动时,其文件描述符STDOUT_FILENO应该是管道的可写端,而文件描述符fileno(stream)在调用进程中,其中stream是popen()返回的流指针,应该是管道的可读端。
- 如果mode是w,当子进程启动时,它的文件描述符STDIN_FILENO应该是管道的可读端,而调用进程中的文件描述符fileno(stream),其中stream是popen()返回的流指针,是管道的可写端。
- 如果mode是任何其他值,则结果未指定。
任何可移植的代码都不会做出任何假设。 BSD popen()
与您的问题描述的类似。
另外,管道与套接字不同,每个管道文件描述符都是单向的。 您必须创建两个管道,一个为每个方向配置。
无需在每个进程中创建两个管道并浪费文件描述符。 只需使用套接字即可。 https://stackoverflow.com/a/25177958/894520
在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中使用相同的代码同时读写 )