Popen()可以制作双向管() + 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()
仅允许模式参数指定阅读或写作,而不是两者兼而有之。因为popen()
现在使用双向管实现,模式参数可以请求双向数据流。该模式参数是指零终止字符串的指针,必须是“ r”,用于读取的“ w”或“ r+”以进行阅读和写作。
解决方案
您似乎已经回答了自己的问题。如果您的代码需要在不支持的旧系统上工作 popen
打开双向管,那么您将无法使用 popen
(至少不是提供的)。
真正的问题是关于所讨论的旧系统的确切功能。特别是他们 pipe
支持创建双向管?如果他们有一个 pipe
可以创建双向管,但是 popen
那不是,那么我会编写代码的主流 popen
带有双向管,并提供 popen
可以使用双向管,该双管在需要时被用在使用的情况下。
如果您需要足够老化的系统,以至于 pipe
仅支持单向管,然后您几乎坚持使用 pipe
, fork
, dup2
, 等等,自己。我可能仍然将其包裹在有效的功能中 几乎 就像现代版本的 popen
, ,但没有返回一个文件句柄,而是用两个文件柄填充一个小结构,一个用于孩子的 stdin
, ,另一个用于孩子的 stdout
.
其他提示
我建议写自己的功能,为您进行管道/分叉/系统效果。您可以将函数产生一个过程并返回读/写文件描述符,如...
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; /* ? */
}
您可以在其中添加所需的任何功能。
posix规定了 popen()
呼叫并非旨在提供双向通信:
popen()的模式参数是指定I/O模式的字符串:
- 如果模式为r,则在启动子进程时,其文件描述符stdout_fileno应为管道的可写端,并且在调用过程中的文件描述符fileNo(流),其中流是popen()返回的流指针,应为管道的可读端。
- 如果模式为w,则在启动子进程时,其文件描述符stdin_fileno应为管道的可读端,并且在调用过程中的文件描述符fileNo(流),其中流是popen()返回的流指针,成为管道的可写末端。
- 如果模式是任何其他值,则结果未指定。
任何便携式代码都不会做出任何假设。 BSD popen()
与您的问题所描述的相似。
此外,管道与插座不同,每个管道文件描述符都是单向的。您必须创建两个管道,一个为每个方向配置。
在一个 Netresolve 后端我正在与脚本交谈,因此我需要写信给它 stdin
并从中读取 stdout
. 。以下功能用stdin执行命令,并将其重定向到管道。您可以使用它并将其适应您的喜好。
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
(我在 同时读写)
无需在每个过程中创建两个管道并浪费归档的标题。只需使用插座即可。 https://stackoverflow.com/a/25177958/894520