进程间通信--popen函数和pclose函数blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=25940216&id=3206312
来源:互联网 发布:popcorn time mac 编辑:程序博客网 时间:2024/06/08 05:37
分类: 系统运维
因为一个普遍的操作是为另一个进程创建一个管道,或者读它的输出或向它发送输入,所以标准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函数blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=25940216&id=3206312
- http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=22891521&id=2109284
- 函数模板(Function Template)及带默认参数的函数 http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=25520556&id
- 进程,轻量级进程,内核线程,用户线程的区别关系http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=27025492&id=3441601
- ambari,hadoop本地源 部署http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=26230811&id=4023821
- MongoDB sharding模式实现(http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=28266791&id=5758139 )
- linux常用c函数 进程操作篇 http://blog.chinaunix.net/uid-25906157-id-3136757.html
- setuid函数的学习笔记 转自:http://blog.chinaunix.net/space.php?uid=1877180&do=blog&cuid=1132108
- 修饰函数和函数返回值的const的差别 http://blog.chinaunix.net/uid-12673432-id-2923858.html
- 50个c/c++源代码网站(转自http://blog.chinaunix.net/space.php?uid=22830296&do=blog&id=1768388)
- ORACLE中的物化视图(原文链接http://blog.chinaunix.net/space.php?uid=8329266&do=blog&id=2032099)
- syslog函数详解 http://blog.chinaunix.net/uid-10540984-id-3506091.html
- linux socket编程之socket()函数介绍 http://blog.chinaunix.net/uid-20788470-id-1841640.html
- epoll源码分析---sys_epoll_create()函数 http://blog.chinaunix.net/uid-28443939-id-3470593.html
- inux 下system函数原型:http://blog.chinaunix.net/uid-29191363-id-4020441.html
- rand与srand函数的使用 http://blog.chinaunix.net/uid-25906157-id-3165140.html
- 纯虚函数 http://blog.chinaunix.net/uid-20585352-id-62720.html
- 虚函数及继承 http://blog.chinaunix.net/uid-25132162-id-1564955.html
- border的妙用:纯CSS绘制三角形
- 开放者眼中的wifi众联开放平台
- String中的contains()方法
- ExtJS vs AngularJS
- 欢迎使用CSDN-markdown编辑器
- 进程间通信--popen函数和pclose函数blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=25940216&id=3206312
- CentOS 6.6下的J2EE环境搭建(二)之Tomcat安装
- 深度学习Matlab工具箱代码详解
- V9站点名称的调用方法
- nyoj 239 月老的难题(匈牙利算法+邻接表)
- “异常处理”学习小结
- malloc 实现二维数组
- Inno Setup 检查安装VS2005运行环境
- java编程习惯