进程间通信--popen函数和pclose函数blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=25940216&id=3206312
2015-09-23 10:01
453 查看
分类: 系统运维
进程间通信--popen函数和pclose函数
因为一个普遍的操作是为另一个进程创建一个管道,或者读它的输出或向它发送输入,所以标准I/O库历史上提供了popen和pclose函数。这两 个函数处理我们自己一直在做的脏活:创建一个管道、fork一个子进程、关闭管道无用的端,执行一个外壳来运行这个命令,等待命令终止。
#include <stdio.h>
FILE *popen(const char *cmdstring, const char *type);
成功返回文件指针,错误返回NULL。
int pclose(FILE *fp);
返回cmdstring的终止状态,错误返回-1。
函数popen执行一个fork和exec来执行cmdstring,并返回一个标准I/O文件指针。如果type是“r”,那么文件指针被连接到cmdstring的标准输入。
如果type是“w”,那么文件指针被连接到cmdstring的标准输入。
一种记住popen的最后一个参数的方法是:像fopen一样,返回的文件指针在“r”的type时是可读的,或在“w”的type时是可写的。
pclose函数关闭标准I/O流,等待命令的终止,返回外壳的终止状态。(我们在8.6节描述过终止状态。system函数,8.13节,也返回终止状态。)如果外壳不能被执行,pclose返回的状态就好像外壳执行了一个exit(127)。
cmdstring被Bourne shell,如
sh -c cmdstring
这意味着外壳展开了cmdstring里的任何特殊字符。例如,这允许我们说:fp = popen("ls *.c", "r");或fp = popen("cmd 2>&1", "r");
让我们用popen重新实现15.2节的第二个程序。
#include <stdio.h>
#define PAGER "${GAGER:-more}" /* environment variable, or default */
#define MAXLINE 4096
int
main(int argc, char *argv[])
{
char line[MAXLINE];
FILE *fpin, *fpout;
if (argc != 2) {
printf("usage: a.out \n");
exit(1);
}
if ((fpin = fopen(argv[1], "r")) == NULL) {
printf("can't open %s\n", argv[1]);
exit(1);
}
if ((fpout = popen(PAGER, "w")) == NULL) {
printf("popen error\n");
exit(1);
}
/* copy argv[1] to pager */
while (fgets(line, MAXLINE, fpin) != NULL) {
if (fputs(line, fpout) == EOF) {
printf("fputs error to pipe\n");
exit(1);
}
}
if (ferror(fpin)) {
printf("fgets error\n");
exit(1);
}
if (pclose(fpout) == -1) {
printf("pclose error\n");
exit(1);
}
exit(0);
}
使用popen减少了我们必须写的代码量。
外壳命令${PAGER:-more}说如果这个外壳变量PAGER被定义且非空则使用它,否则使用字符串more。
下面的代码展示了popen和pclose的我们的版本。
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
/*
* Pointer to array allocated at run-time.
*/
static pid_t *childpid = NULL;
/*
* From our open_max(), Section
2.5.
*/
static int maxfd;
FILE *
popen(const char *cmdstring, const char *type)
{
int i;
int pfd[2];
pid_t pid;
FILE *fp;
/* only allow "r" or "w" */
if ((type[0] != 'r' && type[0] != 'w') || type[1] != 0) {
errno = EINVAL; /* required
by POSIX */
return(NULL);
}
if (childpid == NULL) { /* first time through */
/* allocate zeroed out array for child
pids */
maxfd = open_max();
if ((childpid = calloc(maxfd, sizeof(pid_t))) == NULL)
return(NULL);
}
if (pipe(pfd) < 0)
return(NULL); /* errno set by
pipe() */
if ((pid = fork()) < 0) {
return(NULL); /* errno set by
fork() */
} else if (pid == 0) { /* child */
if (*type == 'r') {
close(pfd[0]);
if (pfd[1] != STDOUT_FILENO) {
dup2(pfd[1], STDOUT_FILENO);
close(pfd[1]);
}
} else {
close(pfd[1]);
if (pfd[0] != STDIN_FILENO) {
dup2(pfd[0], STDIN_FILENO);
close(pfd[0]);
}
}
/* close all descriptors in childpid[] */
for (i = 0; i < maxfd; i++)
if (childpid[i] > 0)
close(i);
execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
_exit(127);
}
/* parent continues... */
if (*type == 'r') {
close(pfd[1]);
if ((fp = fdopen(pfd[0], type)) == NULL)
return(NULL);
} else {
close(pfd[0]);
if ((fp = fdopen(pfd[1], type)) == NULL)
return(NULL);
}
childpid[fileno(fp)] = pid; /* remember
child pid for this fd */
return(fp);
}
int
pclose(FILE *fp)
{
int fd, stat;
pid_t pid;
if (childpid == NULL) {
errno = EINVAL;
return(-1); /* popen() has
never been called */
}
fd = fileno(fp);
if ((pid = childpid[fd]) == 0) {
errno = EINVAL;
return(-1); /* fp
wasn't opened by popen() */
}
childpid[fd] = 0;
if (fclose(fp) == EOF)
return(-1);
while (waitpid(pid, &stat, 0) < 0)
if (errno != EINTR)
return(-1); /* error other
than EINTR from waitpid() */
return(stat); /* return
child's termination status */
}
尽管popen的核心和我们在本章前面使用的代码相似,但是有许多我们需要小心的细节。首先,每个popen被调用时,我们必须记住我们创建 的子进程的进程ID和它的文件描述符或FILE指针。我们选择在childpid数组里存储子进程的ID,并索引它来得到文件描述符。通过这种方法,当 pclose在用FILE指针作为参数被调用时我们调用标准I/O函数fileno来得到文件描述符,然后把子进程ID用在waitpid调用里。因为一 个组宣进程不只一次调用popen是可能的,所以我们动态分配childpid数组(在第一次popen被调用时),它有足够大的空间来容纳和文件描述符
数量相同的子进程。
调用pipe和fork然后为每个进程复制恰当的描述符和我们在本章前面做的事件相似。
POSIX.1要求popen关闭任何在子进程里通过上次popen调用打开的流。为了做到这个,我们遍历子进程里的childpid数组,关掉任何仍然打开的描述符。
如 果pclose调用者已为SIGCHLD设立一个信号处理机会发生什么?pclose里的waitpid调用会返回EINTR的错误。因为调用者被允许捕 获这个信号(或任何可能中断waitpid的其它信号),所以我们简单地再次调用waitpid,如果它被一个捕获的信号中断。
注意如果应用调用waitpid并获得popen创建的子进程的退出状态,那么我们将在应用调用pclose的时候调用waitpid,发现子进程不再存在,返回-1并设置errno为ECHILD。这是POSIX.1在这种情况所要求的行为。
pclose的早期版本返回一个EINTR的错误,如果一个信号中断了wait。同样,一些早期版本的plose在wait期间阻塞或忽略信号SIGINT、SIGQUIT和SIGHUP。这不被POSIX.1允许。
注 意popen决不应该被一个设置用户ID或设置组ID程序调用。当它执行命令时,popen做等价于execl("/bin/sh", "sh", "-c", command, NULL);的事,它用从调用者继承下来的环境执行外壳和command。一个恶意用户可以操作环境,以便外壳执行不被期望的命令,使用从设置ID文件模 式得到的权限。
popen特别适合的事是执行简单的过滤器来转换运行的命令的输入或输出。这是一个命令想要建立自己的管道的情况。
考 虑一个向标准输出写一个提示并从标准输入读一任的应用。使用popen,我们可以在应用和它的输入之间插入一个程序来转换输入。这些进程的排列为:父进程 创建一个子进程运行这个过滤器,并创建管道,使过滤器的标准输出变为管道的写端。父进程向用户终端输出提示,用户通过终端向过滤器输入,而过滤器的输出通 过管道,被父进程读取。
例如,这个转换可以是路径名扩展,或者提供一个历史机制(记住前一个输入的命令)。
下面的代码展示了一个简单的过滤器来证明这个操作。这个过滤拷贝标准输入到标准输出,把任何大写字符轮换为小写。在写一个换行符我们小心地ffush标准输出的原因在下节谈到协进程时讨论。
#include <stdio.h>
int
main(void)
{
int c;
while ((c = getchar()) != EOF) {
if (isupper(c))
c = tolower(c);
if (putchar(c) == EOF) {
printf("output error\n");
exit(1);
}
if (c == '\n')
fflush(stdout);
}
exit(0);
}
我们把这个过滤器编译为可执行文件filter_upper_to_lower,我们在下面代码里使用popen调用它。
#include <stdio.h>
#define MAXLINE 4096
int main(void)
{
char line[MAXLINE];
FILE *fpin;
if ((fpin = popen("./filter_upper_to_lower", "r")) == NULL) {
printf("popen error\n");
exit(1);
}
for (;;) {
fputs("prompt> ", stdout);
fflush(stdout);
if (fgets(line, MAXLINE, fpin) == NULL) /* read
from pipe */
break;
if (fputs(line, stdout) == EOF) {
printf("fputs error to pipe\n");
exit(1);
}
}
if (pclose(fpin) == -1) {
printf("pclose error\n");
exit(1);
}
putchar('\n');
exit(0);
}
我们需要在写提示后调用fflush,因为标准输出通常是行缓冲的,而提示没有包行一个换行符。
进程间通信--popen函数和pclose函数
因为一个普遍的操作是为另一个进程创建一个管道,或者读它的输出或向它发送输入,所以标准I/O库历史上提供了popen和pclose函数。这两 个函数处理我们自己一直在做的脏活:创建一个管道、fork一个子进程、关闭管道无用的端,执行一个外壳来运行这个命令,等待命令终止。
#include <stdio.h>
FILE *popen(const char *cmdstring, const char *type);
成功返回文件指针,错误返回NULL。
int pclose(FILE *fp);
返回cmdstring的终止状态,错误返回-1。
函数popen执行一个fork和exec来执行cmdstring,并返回一个标准I/O文件指针。如果type是“r”,那么文件指针被连接到cmdstring的标准输入。
如果type是“w”,那么文件指针被连接到cmdstring的标准输入。
一种记住popen的最后一个参数的方法是:像fopen一样,返回的文件指针在“r”的type时是可读的,或在“w”的type时是可写的。
pclose函数关闭标准I/O流,等待命令的终止,返回外壳的终止状态。(我们在8.6节描述过终止状态。system函数,8.13节,也返回终止状态。)如果外壳不能被执行,pclose返回的状态就好像外壳执行了一个exit(127)。
cmdstring被Bourne shell,如
sh -c cmdstring
这意味着外壳展开了cmdstring里的任何特殊字符。例如,这允许我们说:fp = popen("ls *.c", "r");或fp = popen("cmd 2>&1", "r");
让我们用popen重新实现15.2节的第二个程序。
#include <stdio.h>
#define PAGER "${GAGER:-more}" /* environment variable, or default */
#define MAXLINE 4096
int
main(int argc, char *argv[])
{
char line[MAXLINE];
FILE *fpin, *fpout;
if (argc != 2) {
printf("usage: a.out \n");
exit(1);
}
if ((fpin = fopen(argv[1], "r")) == NULL) {
printf("can't open %s\n", argv[1]);
exit(1);
}
if ((fpout = popen(PAGER, "w")) == NULL) {
printf("popen error\n");
exit(1);
}
/* copy argv[1] to pager */
while (fgets(line, MAXLINE, fpin) != NULL) {
if (fputs(line, fpout) == EOF) {
printf("fputs error to pipe\n");
exit(1);
}
}
if (ferror(fpin)) {
printf("fgets error\n");
exit(1);
}
if (pclose(fpout) == -1) {
printf("pclose error\n");
exit(1);
}
exit(0);
}
使用popen减少了我们必须写的代码量。
外壳命令${PAGER:-more}说如果这个外壳变量PAGER被定义且非空则使用它,否则使用字符串more。
下面的代码展示了popen和pclose的我们的版本。
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
/*
* Pointer to array allocated at run-time.
*/
static pid_t *childpid = NULL;
/*
* From our open_max(), Section
2.5.
*/
static int maxfd;
FILE *
popen(const char *cmdstring, const char *type)
{
int i;
int pfd[2];
pid_t pid;
FILE *fp;
/* only allow "r" or "w" */
if ((type[0] != 'r' && type[0] != 'w') || type[1] != 0) {
errno = EINVAL; /* required
by POSIX */
return(NULL);
}
if (childpid == NULL) { /* first time through */
/* allocate zeroed out array for child
pids */
maxfd = open_max();
if ((childpid = calloc(maxfd, sizeof(pid_t))) == NULL)
return(NULL);
}
if (pipe(pfd) < 0)
return(NULL); /* errno set by
pipe() */
if ((pid = fork()) < 0) {
return(NULL); /* errno set by
fork() */
} else if (pid == 0) { /* child */
if (*type == 'r') {
close(pfd[0]);
if (pfd[1] != STDOUT_FILENO) {
dup2(pfd[1], STDOUT_FILENO);
close(pfd[1]);
}
} else {
close(pfd[1]);
if (pfd[0] != STDIN_FILENO) {
dup2(pfd[0], STDIN_FILENO);
close(pfd[0]);
}
}
/* close all descriptors in childpid[] */
for (i = 0; i < maxfd; i++)
if (childpid[i] > 0)
close(i);
execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
_exit(127);
}
/* parent continues... */
if (*type == 'r') {
close(pfd[1]);
if ((fp = fdopen(pfd[0], type)) == NULL)
return(NULL);
} else {
close(pfd[0]);
if ((fp = fdopen(pfd[1], type)) == NULL)
return(NULL);
}
childpid[fileno(fp)] = pid; /* remember
child pid for this fd */
return(fp);
}
int
pclose(FILE *fp)
{
int fd, stat;
pid_t pid;
if (childpid == NULL) {
errno = EINVAL;
return(-1); /* popen() has
never been called */
}
fd = fileno(fp);
if ((pid = childpid[fd]) == 0) {
errno = EINVAL;
return(-1); /* fp
wasn't opened by popen() */
}
childpid[fd] = 0;
if (fclose(fp) == EOF)
return(-1);
while (waitpid(pid, &stat, 0) < 0)
if (errno != EINTR)
return(-1); /* error other
than EINTR from waitpid() */
return(stat); /* return
child's termination status */
}
尽管popen的核心和我们在本章前面使用的代码相似,但是有许多我们需要小心的细节。首先,每个popen被调用时,我们必须记住我们创建 的子进程的进程ID和它的文件描述符或FILE指针。我们选择在childpid数组里存储子进程的ID,并索引它来得到文件描述符。通过这种方法,当 pclose在用FILE指针作为参数被调用时我们调用标准I/O函数fileno来得到文件描述符,然后把子进程ID用在waitpid调用里。因为一 个组宣进程不只一次调用popen是可能的,所以我们动态分配childpid数组(在第一次popen被调用时),它有足够大的空间来容纳和文件描述符
数量相同的子进程。
调用pipe和fork然后为每个进程复制恰当的描述符和我们在本章前面做的事件相似。
POSIX.1要求popen关闭任何在子进程里通过上次popen调用打开的流。为了做到这个,我们遍历子进程里的childpid数组,关掉任何仍然打开的描述符。
如 果pclose调用者已为SIGCHLD设立一个信号处理机会发生什么?pclose里的waitpid调用会返回EINTR的错误。因为调用者被允许捕 获这个信号(或任何可能中断waitpid的其它信号),所以我们简单地再次调用waitpid,如果它被一个捕获的信号中断。
注意如果应用调用waitpid并获得popen创建的子进程的退出状态,那么我们将在应用调用pclose的时候调用waitpid,发现子进程不再存在,返回-1并设置errno为ECHILD。这是POSIX.1在这种情况所要求的行为。
pclose的早期版本返回一个EINTR的错误,如果一个信号中断了wait。同样,一些早期版本的plose在wait期间阻塞或忽略信号SIGINT、SIGQUIT和SIGHUP。这不被POSIX.1允许。
注 意popen决不应该被一个设置用户ID或设置组ID程序调用。当它执行命令时,popen做等价于execl("/bin/sh", "sh", "-c", command, NULL);的事,它用从调用者继承下来的环境执行外壳和command。一个恶意用户可以操作环境,以便外壳执行不被期望的命令,使用从设置ID文件模 式得到的权限。
popen特别适合的事是执行简单的过滤器来转换运行的命令的输入或输出。这是一个命令想要建立自己的管道的情况。
考 虑一个向标准输出写一个提示并从标准输入读一任的应用。使用popen,我们可以在应用和它的输入之间插入一个程序来转换输入。这些进程的排列为:父进程 创建一个子进程运行这个过滤器,并创建管道,使过滤器的标准输出变为管道的写端。父进程向用户终端输出提示,用户通过终端向过滤器输入,而过滤器的输出通 过管道,被父进程读取。
例如,这个转换可以是路径名扩展,或者提供一个历史机制(记住前一个输入的命令)。
下面的代码展示了一个简单的过滤器来证明这个操作。这个过滤拷贝标准输入到标准输出,把任何大写字符轮换为小写。在写一个换行符我们小心地ffush标准输出的原因在下节谈到协进程时讨论。
#include <stdio.h>
int
main(void)
{
int c;
while ((c = getchar()) != EOF) {
if (isupper(c))
c = tolower(c);
if (putchar(c) == EOF) {
printf("output error\n");
exit(1);
}
if (c == '\n')
fflush(stdout);
}
exit(0);
}
我们把这个过滤器编译为可执行文件filter_upper_to_lower,我们在下面代码里使用popen调用它。
#include <stdio.h>
#define MAXLINE 4096
int main(void)
{
char line[MAXLINE];
FILE *fpin;
if ((fpin = popen("./filter_upper_to_lower", "r")) == NULL) {
printf("popen error\n");
exit(1);
}
for (;;) {
fputs("prompt> ", stdout);
fflush(stdout);
if (fgets(line, MAXLINE, fpin) == NULL) /* read
from pipe */
break;
if (fputs(line, stdout) == EOF) {
printf("fputs error to pipe\n");
exit(1);
}
}
if (pclose(fpin) == -1) {
printf("pclose error\n");
exit(1);
}
putchar('\n');
exit(0);
}
我们需要在写提示后调用fflush,因为标准输出通常是行缓冲的,而提示没有包行一个换行符。
相关文章推荐
- Android --Android Stuido混淆签名打包
- GPDB43 Administrator Guide--第七章 扩展greenplum系统
- Request中的方法调用
- Android GUI之View事件处理
- ASINetworkQueues(经典2)
- 在UILable内显示HTML页面内容
- SuiShenJi项目_登录界面
- AlertDialog.Builder
- UIScrollview UIPageViewCon troller
- UI高级第三课  音频视频——iOS学习连载31
- 张瀚荣:如何用UE4制作3D动作游戏
- Nicholas谈UE4高级渲染:动态光照迭代快
- Nicholas谈UE4对手游平台的优化和支持
- UE4手册中文翻译速查表
- Repeated DNA Sequences
- iOS-Core-Animation-Advanced-Techniques(五)
- Customizing Your Build With Gradle
- Android面试题 请解释下单线程模型中Message、Handler、MessageQueue、Looper之间的关系
- 【UIKit-124-3】#import <UIKit/UIView.h>
- codechef Tree and Queries Solved