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只支持单向管道,那么你自己就很难使用pipeforkdup2等。 我可能仍然将它包装在一个几乎像现代版本的popen的函数中,但不是返回一个文件句柄,而是填充一个带有两个文件句柄的小结构,一个用于孩子的stdin ,另一个用于孩子的stdinstdout

我建议您编写自己的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模式的字符串:

  1. 如果mode是r,当子进程启动时,其文件描述符STDOUT_FILENO应该是管道的可写端,而文件描述符fileno(stream)在调用进程中,其中stream是popen()返回的流指针,应该是管道的可读端。
  2. 如果mode是w,当子进程启动时,它的文件描述符STDIN_FILENO应该是管道的可读端,而调用进程中的文件描述符fileno(stream),其中stream是popen()返回的流指针,是管道的可写端。
  3. 如果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中使用相同的代码同时读写 )