管道是Linux支持的最初Unix IPC形式之一,具有以下特点:
该函数创建的管道的两端处于一个进程中间,在实际应用中没有太大意义,因此,一个进程在由pipe()创建管道后,一般再fork一个子进程,然后通过管道实现父子进程间的通信(因此也不难推出,只要两个进程中存在亲缘关系,这里的亲缘关系指的是具有共同的祖先,都可以采用管道方式来进行通信)。
管道两端可分别用描述字fd[0]以及fd[1]来描述,需要注意的是,管道的两端是固定了任务的。即一端只能用于读,由描述字fd[0]表示,称其为管道读端;另一端则只能用于写,由描述字fd[1]来表示,称其为管道写端。如果试图从管道写端读取数据,或者向管道读端写入数据都将导致错误发生。一般文件的I/O函数都可以用于管道,如close、read、write等等。
从管道中读取数据:
关于管道的读规则验证:
#include <unistd.h> #include <sys/types.h> #include <errno.h> #include <stdlib.h> #include <string.h> #include <stdio.h> main() { int pipe_fd[2]; pid_t pid; char r_buf[100]; char w_buf[4]; char *p_wbuf; int r_num; int cmd; memset(r_buf, 0, sizeof(r_buf)); memset(w_buf, 0, sizeof(r_buf)); p_wbuf = w_buf; if(pipe(pipe_fd) < 0) { printf("pipe create error\n"); return -1; } if((pid = fork()) == 0) { printf("\n"); close(pipe_fd[1]); sleep(3); //确保父进程关闭写端 r_num = read(pipe_fd[0], r_buf, 100); printf("read num is %d ,the data read from the pipe is %d\n", r_num, atoi(r_buf)); close(pipe_fd[0]); exit(0); } else if(pid > 0) { close(pipe_fd[0]);//父进程管道读端关闭了,但是子进程的读端没有关闭,这样就使得父子进程之间可以进行通信 strcpy(w_buf,"111"); if(write(pipe_fd[1], w_buf, 4) != -1) printf("parent write over\n"); close(pipe_fd[1]);//close write printf("parent close fd[1] over\n"); sleep(10); } }
程序输出结果:
附加结论:管道写端关闭后,写入的数据将一直存在,直到读出为止。
向管道中写入数据:
管道的写规则的验证一:
#include <unistd.h> #include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <string.h> main() { int pipe_fd[2]; pid_t pid; char r_buf[4]; char *w_buf; int writenum; int cmd; memset(r_buf, 0, sizeof(r_buf)); if(pipe(pipe_fd) < 0) { printf("pipe create error\n"); return -1; } if((pid = fork()) == 0) {//子进程 close(pipe_fd[0]);//读端关闭 close(pipe_fd[1]);//写端关闭 sleep(10); exit(0); } else if(pid > 0) { //父进程 sleep(1); close(pipe_fd[0]);//父进程的管道读端已经关闭,子进程的管道读端也已经关闭,所以无法写入 w_buf="111"; if((writenum = write(pipe_fd[1], w_buf, 4)) == -1) printf("write to pipe error\n"); else printf("the bytes write to pipe is %d \n", writenum); close(pipe_fd[1]); } }
则输出结果为空:
原因就是该管道以及它的所有fork()产物的读端都已经被关闭。如果在父进程中保留读端,即在写完pipe后,再关闭父进程的读端,也会正常写入pipe,读者可自己验证一下该结论。(将上面的代码改成这样:)
if((pid = fork()) == 0) {//子进程 close(pipe_fd[0]);//读端关闭 close(pipe_fd[1]);//写端关闭 sleep(10); exit(0); } else if(pid > 0) {//父进程 sleep(5); //虽然子进程的管道读端与写端都关闭了,但是父进程的管道读端没有关闭,所以还是可以写入的 w_buf="111"; if((writenum = write(pipe_fd[1], w_buf, 4)) == -1) printf("write to pipe error\n"); else printf("the bytes write to pipe is %d \n", writenum); close(pipe_fd[1]); }输出就会这样:
因此,在向管道写入数据时,至少应该存在某一个进程,其中管道读端没有被关闭(可以是父进程的管道读端,也可以是子进程的管道读端),否则就会出现上述错误(管道断裂,进程收到了SIGPIPE信号,默认动作是进程终止)
对管道的写规则的验证二:
#include <unistd.h> #include <sys/types.h> #include <errno.h> #include <string.h> #include <stdio.h> #include <stdlib.h> main(int argc, char **argv) { int pipe_fd[2]; pid_t pid; char r_buf[4096]; char w_buf[4096 * 2]; int writenum; int rnum; int count; memset(r_buf, 0, sizeof(r_buf)); if(pipe(pipe_fd) < 0) { printf("pipe create error\n"); return -1; } if((pid = fork()) == 0) { close(pipe_fd[1]);//read count=0; while(count<10) { sleep(1); rnum = read(pipe_fd[0], r_buf, 1000); printf("child: readnum is %d\n", rnum); count++; } close(pipe_fd[0]); exit(0); } else if(pid > 0) { close(pipe_fd[0]); //write memset(r_buf, 0, sizeof(r_buf)); if((writenum = write(pipe_fd[1], w_buf, 1024)) == -1) printf("write to pipe error\n"); else printf("the bytes write to pipe is %d \n", writenum); writenum = write(pipe_fd[1], w_buf, 4096); close(pipe_fd[1]); } }
输出结果:
结论: 写入数目小于4096时写入是非原子的!(原作者的输出是下面这样的:)
the bytes write to pipe is 1000
the bytes write to pipe is 1000
the bytes write to pipe is 1000
the bytes write to pipe is 1000
the bytes write to pipe is 1000
the bytes write to pipe is 120
the bytes write to pipe is 0
the bytes write to pipe is 0 ......
如果结果真是如此,这些输出倒是的确能证明写入数目小于4096时写入是非原子的!可是我运行程序后输出不是这样的。奇怪的是作者为什么得出那样的输出。思考中……
原文还说:如果把父进程中的两次写入字节数都改为5000,则很容易得出下面结论:写入管道的数据量大于4096字节时,缓冲区的空闲空间将被写入数据(补齐),直到写完所有数据为止,如果没有进程读数据,则一直阻塞。