在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输出的内容
用图表示如下:
2、如果type=w,则调用进程将内容写到cmd命令的标准输入中,作为cmd命令的标准输入
那么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实现