#include<unistd.h> int pipe(int fd[2])
若是成功,则返回值是0,若是失败,则返回值是-1,而且设置errno。
成功调用pipe函数以后,会返回两个打开的文件描述符,一个是管道的读取端描述符pipefd[0],另外一个是管道的写入端描述符pipefd[1]。管道没有文件名与之关联,所以程序没有选择,只能经过文件描述符来访问管道,只有那些能看到这两个文件描述符的进程才可以使用管道。那么谁能看到进程打开的文件描述符呢?只有该进程及该进程的子孙进程才能看到。这就限制了管道的使用范围。程序员
write(pipefd[1],wbuf,count);
一旦向管道的写入端写入数据后,就能够对读取端描述符pipefd[0]调用read,读出管道里面的内容。以下所示,管道上的read调用返回的字节数等于请求字节数和管道中当前存在的字节数的最小值。若是当前管道为空,那么read调用会阻塞(若是没有设置O_NONBLOCK标志位的话)。shell
调用pipe函数返回的两个文件描述符中,读取端pipefd[0]支持的文件操做定义在read_pipefifo_fops,写入端pipefd[1]支持的文件操做定义在write_pipefifo_fops,其定义以下:编程
const struct file_operations read_pipefifo_fops = { //读端相关操做 .llseek = no_llseek, .read = do_sync_read, .aio_read = pipe_read, .write = bad_pipe_w, //一旦写,将调用bad_pipe_w .poll = pipe_poll, .unlocked_ioctl = pipe_ioctl, .open = pipe_read_open, .release = pipe_read_release, .fasync = pipe_read_fasync,};const struct file_operations write_pipefifo_fops = {//写端相关操做 .llseek = no_llseek, .read = bad_pipe_r, //一旦读,将调用bad_pipe_r .write = do_sync_write, .aio_write = pipe_write, .poll = pipe_poll, .unlocked_ioctl = pipe_ioctl, .open = pipe_write_open, .release = pipe_write_release, .fasync = pipe_write_fasync,};
咱们能够看到,对读取端描述符执行write操做,内核就会执行bad_pipe_w函数;对写入端描述符执行read操做,内核就会执行bad_pipe_r函数。这两个函数比较简单,都是直接返回-EBADF。所以对应的read和write调用都会失败,返回-1,并置errno为EBADF。async
static ssize_t bad_pipe_r(struct file filp, char __user buf, size_t count, loff_t ppos) { return -EBADF; //返回错误 } static ssize_t bad_pipe_w(struct file filp, const char __user buf, size_t count,loff_t ppos) { return -EBADF; }
咱们只介绍了pipe函数接口,至今尚看不出来该如何使用pipe函数进行进程间通讯。调用pipe以后,进程发生了什么呢?请看图9-5。
函数
能够看到,调用pipe函数以后,系统给进程分配了两个文件描述符,即pipe函数返回的两个描述符。该进程既能够往写入端描述符写入信息,也能够从读取端描述符读出信息。但是一个进程管道,起不到任何通讯的做用。这不是通讯,而是自言自语。
若是调用pipe函数的进程随后调用fork函数,建立了子进程,状况就不同了。fork之后,子进程复制了父进程打开的文件描述符(如图9-6所示),两条通讯的通道就创建起来了。此时,能够是父进程往管道里写,子进程从管道里面读;也能够是子进程往管道里写,父进程从管道里面读。这两条通路都是可选的,可是不能都选。缘由前面介绍过,管道里面是字节流,父子进程都写、都读,就会致使内容混在一块儿,对于读管道的一方,解析起来就比较困难。常规的使用方法是父子进程一方只能写入,另外一方只能读出,管道变成一个单向的通道,以方便使用。如图9-7所示,父进程放弃读,子进程放弃写,变成父进程写入,子进程读出,成为一个通讯的通道…
spa
int pipefd[2]; pipe(pipefd); switch(fork()) { case -1: /fork failed, error handler here/ case 0: /子进程/ close(pipefd[1]) ; /关闭掉写入端对应的文件描述符/ /子进程能够对pipefd[0]调用read/ break; default: /父进程/ close(pipefd[0]); /父进程关闭掉读取端对应的文件描述符/ /父进程能够对pipefd[1]调用write, 写入想告知子进程的内容/ break }
if(pipefd[1] != STDOUT_FILENO){dup2(pipefd[1],STDOUT_FILENO);close(pipefd[1]);}
一样的道理,对于第二个子进程,如法炮制:3d
if(pipefd[0] != STDIN_FILENO){dup2(pipefd[0],STDIN_FILENO);close(pipefd[0]);}
简单来讲,就是第一个子进程的标准输出被绑定到了管道的写入端,因而第一个命令的输出,写入了管道,而第二个子进程管道将其标准输入绑定到管道的读取端,只要管道里面有了内容,这些内容就成了标准输入。指针
两个示例代码,为何要判断管道的文件描述符是否等于标准输入和标准输出呢?缘由是,在调用pipe时,进程极可能已经关闭了标准输入和标准输出,调用pipe函数时,内核会分配最小的文件描述符,因此pipe的文件描述符可能等于0或1。在这种状况下,若是没有if判断加以保护,代码就变成了:code
dup2(1,1);close(1);
这样的话,第一行代码什么也没作,第二行代码就把管道的写入端给关闭了,因而便没法传递信息了orm
道的一个重要做用是和外部命令进行通讯。在平常编程中,常常会须要调用一个外部命令,而且要获取命令的输出。而有些时候,须要给外部命令提供一些内容,让外部命令处理这些输入。Linux提供了popen接口来帮助程序员作这些事情。
就像system函数,即便没有system函数,咱们经过fork、exec及wait家族函数同样也能够实现system的功能。但终归是不方便,system函数为咱们提供了一些便利。一样的道理,只用pipe函数及dup2等函数,也能完成popen要完成的工做,但popen接口给咱们提供了便利。
popen接口定义以下:
#include <stdio.h>FILE *popen(const char *command, const char *type);int pclose(FILE *stream);
popen函数会建立一个管道,而且建立一个子进程来执行shell,shell会建立一个子进程来执行command。根据type值的不一样,分红如下两种状况。
若是type是r:command执行的标准输出,就会写入管道,从而被调用popen的进程读到。经过对popen返回的FILE类型指针执行read或fgets等操做,就能够读取到command的标准输出,如图9-10所示。
若是type是w:调用popen的进程,能够经过对FILE类型的指针fp执行write、fputs等操做,负责往管道里面写入,写入的内容通过管道传给执行command的进程,做为命令的输入,如图9-11所示
popen函数成功时,会返回stdio库封装的FILE类型的指针,失败时会返回NULL,而且设置errno。常见的失败有fork失败,pipe失败,或者分配内存失败。
I/O结束了之后,能够调用pclose函数来关闭管道,而且等待子进程的退出。尽管popen函数返回的是FILE类型的指针,也不该调用fclose函数来关闭popen函数打开的文件流指针,由于fclose不会等待子进程的退出。pclose函数成功时会返回子进程中shell的终止状态。popen函数和system函数相似,若是command对应的命令没法执行,就如同执行了exit(127)同样。若是发生其余错误,pclose函数则返回-1。能够从errno中获取到失败的缘由。
下面给出一个简单的例子,来示范下popen的用法:
#include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<string.h>#include<errno.h>#include<sys/wait.h>#include<signal.h>#define MAX_LINE_SIZE 8192void print_wait_exit(int status){ printf("status = %d\n",status); if(WIFEXITED(status)) { printf("normal termination,exit status = %d\n",WEXITSTATUS(status)); } else if(WIFSIGNALED(status)) { printf("abnormal termination,signal number =%d%s\n", WTERMSIG(status),#ifdef WCOREDUMP WCOREDUMP(status)?"core file generated" : "");#else "");#endif }}int main(int argc ,char* argv[]){ FILE *fp = NULL ; char command[MAX_LINE_SIZE],buffer[MAX_LINE_SIZE]; if(argc != 2 ) { fprintf(stderr,"Usage: %s filename \n",argv[0]); exit(1); } snprintf(command,sizeof(command),"cat %s",argv[1]); fp = popen(command,"r"); if(fp == NULL) { fprintf(stderr,"popen failed (%s)",strerror(errno)); exit(2); } while(fgets(buffer,MAX_LINE_SIZE,fp) != NULL) { fprintf(stdout,"%s",buffer); } int ret = pclose(fp); if(ret == 127 ) { fprintf(stderr,"bad command : %s\n",command); exit(3); } else if(ret == -1) { fprintf(stderr,"failed to get child status (%s)\n",strerror(errno)); exit(4); } else { print_wait_exit(ret); } exit(0);}