popen()可以像pipe()+ fork()一样制作双向管道吗?

Tay*_*ton 19 c fork pipe popen dup2

我正在用C++(主要是C)在模拟文件系统上实现管道.它需要在主机shell中运行命令,但在模拟文件系统上执行管道本身.

我可以与实现这一目标pipe(),fork()system()系统调用,但我宁愿使用popen()(它处理创建一条管道,把一个进程,并通过一个命令外壳).这可能是不可能的,因为(我认为)我需要能够从管道的父进程写入,在子进程结束时读取,从子进程写回输出,最后从父进程读取该输出.popen()我的系统上的手册页说明了双向管道,但是我的代码需要在一个只支持单向管道的旧版本的系统上运行.

通过上面的单独调用,我可以打开/关闭管道来实现这一目标.这有可能popen()吗?

对于一个简单的例子,要运行ls -l | grep .txt | grep cmds我需要:

  • 打开管道并ls -l在主机上运行进程; 读回它的输出
  • 将输出管道ls -l传回我的模拟器
  • 打开管道和进程以grep .txt在管道输出上的主机上运行ls -l
  • 将此输出传回模拟器(卡在此处)
  • 打开管道和进程以grep cmds在管道输出上的主机上运行grep .txt
  • 将其输出传回模拟器并打印出来

男人popen

从Mac OS X:

popen()函数通过创建双向管道,分叉和调用shell来"打开"一个过程.由popen() 父进程中先前调用打开的任何流都将在新的子进程中关闭.历史上,popen()使用单向管道实施; 因此,许多实现popen()只允许mode参数指定读或写,而不是两者.因为 popen()现在使用双向管道实现,mode参数可以请求双向数据流.mode参数是一个指向以null结尾的字符串的指针,该字符串必须为'r'表示读取,'w'表示写入,或'r +'表示读写.

sle*_*ica 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; /* ? */
}
Run Code Online (Sandbox Code Playgroud)

您可以在其中添加所需的任何功能.


Jer*_*fin 11

你好像回答了自己的问题.如果您的代码需要在不支持popen打开双向管道的旧系统上工作,那么您将无法使用popen(至少不能使用提供的那个).

真正的问题是关于旧系统的确切功能.特别是,他们pipe支持创建双向管道吗?如果他们有一个pipe可以创建双向管道,但popen没有,那么我会编写代码的主流以用于popen双向管道,并提供一个实现popen,可以使用编译的双向管道在需要的地方使用.

如果您需要支持旧体制不够,pipe只支持单向管道,那么你使用非常卡住pipe,fork,dup2,等,靠自己了.在工作的功能我可能还是把这个包起来几乎就像一个现代版的popen,但不是返回一个文件句柄,在小结构的填充有两个文件句柄,一个孩子的stdin,另一个孩子的stdout.


Jon*_*ler 8

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()与您的问题描述的类似.

另外,管道与套接字不同,每个管道文件描述符都是单向的.您必须创建两个管道,一个为每个方向配置.


Pav*_*rda 5

在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;
}
Run Code Online (Sandbox Code Playgroud)

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

(我在popen同时读写中使用了相同的代码)