unp pipe popen函数
在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实现