首页 文章

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

提问于
浏览
18

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

我可以通过 pipe()fork()system() 系统调用来实现这一点,但我更喜欢使用 popen() (它处理创建管道,分支进程,并将命令传递给shell) . 这可能是不可能的,因为(我认为)我需要能够从管道的父进程写入,在子进程端读取,从子进程写回输出,最后从父进程读取该输出 . 我的系统上的 popen() 的手册页说明了双向管道,但是我的代码需要在一个只支持单向管道的旧版本的系统上运行 .

With the separate calls above, I can open/close pipes to achieve this. Is that possible with popen()?

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

  • 打开一个管道和进程以在主机上运行 ls -l ;读回它的输出

  • ls -l 的输出传回我的模拟器

  • 打开管道并进程以在 ls -l 的管道输出上的主机上运行 grep .txt

  • 将此输出传回模拟器(卡在此处)

  • 打开管道并进程以在 grep .txt 的管道输出上的主机上运行 grep cmds

  • 将此输出传回模拟器并打印出来

man popen

从Mac OS X:

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

5 回答

  • 1

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

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

    如果你需要支持足够大的系统 pipe 只支持单向管道,那么你自己就完全坚持使用 pipeforkdup2 等 . 我可能仍然将它包装在一个几乎像 popen 的现代版本的函数中,但不是返回一个文件句柄,而是填充一个带有两个文件句柄的小结构,一个用于孩子的 stdin ,另一个用于孩子的 stdout .

  • 0

    我建议您编写自己的功能来为您进行管道/分叉/系统管理 . 您可以让函数生成一个进程并返回读/写文件描述符,如...

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

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

  • 8

    POSIX规定popen()呼叫不是为了提供双向通信而设计的:

    popen()的mode参数是一个指定I / O模式的字符串:如果mode是r,则启动子进程时,其文件描述符STDOUT_FILENO应为管道的可写端,文件描述符为fileno(流) )在调用进程中,stream是popen()返回的流指针,应该是管道的可读端 . 如果mode是w,当子进程启动时,它的文件描述符STDIN_FILENO应该是管道的可读端,而调用进程中的文件描述符fileno(stream),其中stream是popen()返回的流指针,是管道的可写端 . 如果mode是任何其他值,则结果未指定 .

    任何可移植的代码都不会做出任何假设 . BSD popen() 与您的问题描述的类似 .

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

  • 10

    无需在每个进程中创建两个管道并浪费文件描述符 . 只需使用套接字即可 . https://stackoverflow.com/a/25177958/894520

  • 19

    在其中一个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

相关问题