现在的位置: 首页 > 综合 > 正文

unp pipe popen函数

2013年10月13日 ⁄ 综合 ⁄ 共 2309字 ⁄ 字号 评论关闭

在unix中pipe是一种很早的进程间通讯的手段,主要用于有血缘关系的进程(例如父子进程等)。pipe是半双工的,即数据流的方向是单向的。关于pipe的内容在unp的pipe和fifo章节进行总结吧,本文主要分析popen函数。

标准I/O库提供了popen函数,原型如下:

FILE *popen(const   char *cmd,  const char *type);

int pclose(FILE  *stream);

其中cmd是一个shell命令行,当调用popen函数时,会创建一个pipe,这个pipe会用在调用进程和cmd shell命令之间进行通讯。popen的返回值是标准I/O的FILE结构,可以用于输入或者输出,具体是输入还是输出由type参数决定:

1、如果type=r,则调用进程从cmd的标准输出中读内容,即读取的内容是cmd输出的内容

用图表示如下:

image

2、如果type=w,则调用进程将内容写到cmd命令的标准输入中,作为cmd命令的标准输入

image

那么popen函数是如何实现的呢?APUE给出了一个版本,google code下搜了一个,如下所示:

#include <sys/param.h>
#include <sys/wait.h>

#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <paths.h>

static struct pid {
        struct pid *next;
        FILE *fp;
        pid_t pid;
} *pidlist;//pid链表,一个进程可以多次使用popen,则要与多个子进程利用pipe进行通讯

FILE *
popen(const char *program, const char *type)
{
        struct pid * volatile cur;
        FILE *iop;
        int pdes[2];    //用于通讯的pipe
        pid_t pid;
         //type需要是r或者w中的一个 
        if ((*type != 'r' && *type != 'w') || type[1] != '/0') {  
                errno = EINVAL;
                return (NULL);
        }

        if ((cur = malloc(sizeof(struct pid))) == NULL)
                return (NULL);

        if (pipe(pdes) < 0) {    //创建用于通讯的pipe
                free(cur);
                return (NULL);
        }

        switch (pid = vfork()) {   //调用vfork,创建子进程,同时保证子进程先运行
        case -1:                        /* Error. */
                (void)close(pdes[0]);
                (void)close(pdes[1]);
                free(cur);
                return (NULL);
                /* NOTREACHED */
        case 0:                         /* Child. */
            {
                struct pid *pcur;
                /*
                 * because vfork() instead of fork(), must leak FILE *,
                 * but luckily we are terminally headed for an execl()
                 */
                for (pcur = pidlist; pcur; pcur = pcur->next)
                        close(fileno(pcur->fp));

                if (*type == 'r') {   //如果type=r
                        int tpdes1 = pdes[1];
                //type=r,则pipe[0]对于子进程是无用的(pipe[0]用于读,pipe[1]用于写)

                        (void) close(pdes[0]);  
                        /*
                         * We must NOT modify pdes, due to the
                         * semantics of vfork.
                         */
                        if (tpdes1 != STDOUT_FILENO) {
                              //将标准输出dup到pipe[1],即将标准输出指向了pipe[1]
                                (void)dup2(tpdes1, STDOUT_FILENO);
                                              //关闭掉pipe[1]描述符,但标准输出未关闭,参考dup函数 
                                (void)close(tpdes1); 
                                tpdes1 = STDOUT_FILENO;
                        }
                } else {   //type=w
                        (void)close(pdes[1]); //关闭写操作
                        if (pdes[0] != STDIN_FILENO) {
                                //将标准输入dup到pipe[0]
                                (void)dup2(pdes[0], STDIN_FILENO)
                                (void)close(pdes[0]);
                        }
                }
                 //执行cmd命令
                execl(_PATH_BSHELL, "sh", "-c", program, (char *)NULL); 
                _exit(127);
                /* NOTREACHED */
            }
        }

        /* Parent; assume fdopen can't fail. */
        if (*type == 'r') {//父进程与子进程类似,type=r
                iop = fdopen(pdes[0], type);//fdopen将文件描述符转换为FILE结构
                (void)close(pdes[1]);//关闭pipe[1]写端
        } else {
                iop = fdopen(pdes[1], type);
                (void)close(pdes[0]);
        }

        /* Link into list of file descriptors. */
        cur->fp = iop;
        cur->pid =  pid;
        cur->next = pidlist;
        pidlist = cur;

        return (iop); //返回标准文件I/O结构
}

当然,上面只是其中的一种实现方式而已,但整体的实现思路是类似的。

参考:google code popen实现、APUE popen实现

抱歉!评论已关闭.