您的位置:首页 > 产品设计 > UI/UE

进程间通信--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,因为标准输出通常是行缓冲的,而提示没有包行一个换行符。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: