管道是一种只允许用在有亲属关系的进程间通信的方式,由函数pipe创建一个管道,read,write进行读写操作。
#include <unistd.h> int pipe(int pipefd[2]);
参数pipefd[2]数组返回打开的读写描述符,pipefd[0]为读,pipefd[1]为写。
第一个问题:文件描述符怎么会出现一个只能读,一个只能写呢?猜想是对一个文件打开了2次,一个以只读打开,一个以只写打开。使用fcntl来验证下:
#include <unistd.h> #include <fcntl.h> int fcntl(int fd, int cmd, ... /* arg */ );
F_GETFL (void) Get the file access mode and the file status flags; arg is ignored.
cmd为F_GETFL时,最后一个参数arg被忽略。测试代码:
1 #include <sys/types.h> 2 #include <sys/stat.h> 3 #include <stdio.h> 4 #include <fcntl.h> 5 #include <signal.h> 6 #include <unistd.h> 7 #include <stdlib.h> 8 9 int main(void) 10 { 11 int flags; 12 int fd[2]; 13 14 if (pipe(fd) < 0) 15 { 16 perror("pipe error"); 17 } 18 19 flags = fcntl(fd[0], F_GETFL,0); 20 if ( flags < 0 ) 21 { 22 perror("fcntl"); 23 close(fd[0]); 24 close(fd[1]); 25 } 26 switch (flags & O_ACCMODE) 27 { 28 case O_RDONLY: 29 printf("read only "); 30 break; 31 32 case O_WRONLY: 33 printf("write only "); 34 break; 35 36 case O_RDWR: 37 printf("read write "); 38 break; 39 40 default: 41 printf("unknown access mode "); 42 } 43 44 flags = fcntl(fd[1], F_GETFL,0); 45 if ( flags < 0 ) 46 { 47 perror("fcntl"); 48 close(fd[0]); 49 close(fd[1]); 50 } 51 switch (flags & O_ACCMODE) 52 { 53 case O_RDONLY: 54 printf("read only "); 55 break; 56 57 case O_WRONLY: 58 printf("write only "); 59 break; 60 61 case O_RDWR: 62 printf("read write "); 63 break; 64 65 default: 66 printf("unknown access mode "); 67 } 68 close(fd[0]); 69 close(fd[1]); 70 exit(0); 71 }
运行结果:
read only
write only
与猜想相符。
数据的流向:
从图中可以看出,进程可以以pipefd[1]写完,然后以pipefd[0]读,自己写自己读,这条数据流是通的。 验证:
1 #include <sys/types.h> 2 #include <sys/stat.h> 3 #include <stdio.h> 4 #include <fcntl.h> 5 #include <signal.h> 6 #include <unistd.h> 7 #include <stdlib.h> 8 9 #define MAXLINE 4096 10 int main(void) 11 { 12 int flags; 13 int fd[2], n; 14 char buf[MAXLINE]; 15 if (pipe(fd) < 0) 16 { 17 perror("pipe error"); 18 } 19 20 n = write(fd[1], "hello world ", MAXLINE); 21 if ( n < 0 ) 22 { 23 perror("write"); 24 goto end; 25 } 26 n = read(fd[0],buf, n); 27 if ( n < 0 ) 28 { 29 perror("read"); 30 goto end; 31 } 32 printf("read:%s ",buf); 33 34 end: 35 close(fd[0]); 36 close(fd[1]); 37 exit(0); 38 }
输出:
read:hello world
既然是进程间通信,那么管道在同一个进程中读写基本是没什么意义的,管道常用的方式是,先创建一个管道,然后fork,父子进程就共享了这个管道了。数据流向如图:
这样,管道的写端有2个进程操作,读端有2个进程操作。但是这样一来就出现了一个问题,假设父进程读,那么这个数据是它自己写进去的呢?还是子进程写进去的?无法区分。通常一个进程关闭它的读,另一个进程关闭它的写,这样,数据流向就只有一个方向了,数据来自谁就显而易见了。如图:
测试代码:
1 #include <sys/types.h> 2 #include <sys/stat.h> 3 #include <stdio.h> 4 #include <fcntl.h> 5 #include <signal.h> 6 #include <unistd.h> 7 #include <stdlib.h> 8 #include <string.h> 9 10 #define MAXLINE 4096 11 12 int main(void) 13 { 14 int n; 15 int fd[2]; 16 pid_t pid; 17 char line[MAXLINE]; 18 19 if (pipe(fd) < 0) 20 perror("pipe error"); 21 if ((pid = fork()) < 0) 22 { 23 perror("fork error"); 24 } 25 else if (pid > 0) /* parent */ 26 { 27 close(fd[0]); 28 write(fd[1], "hello world ", 12); 29 } 30 else /* child */ 31 { 32 close(fd[1]); 33 n = read(fd[0], line, MAXLINE); 34 write(STDOUT_FILENO, line, n); 35 } 36 exit(0); 37 }
结果:
hello world
读一个空的管道或者写一个满的管道都将导致阻塞,不过可以通过fcntl的F_SETFL设置为O_NONBLOCK,从而不阻塞。
当管道一端被关闭后,有下列2条规则:
1.当读一个写端所有文件描述符引用都已被关闭的管道时,在所有数据被读完后,read将返回0。表示无数据可读。
2.当写一个读端所有文件描述符引用都已被关闭的管道时,将产生SIGPIPE信号,write返回-1。
混淆的东西,管道的容量和管道的缓冲区大小。
管道的容量:指管道满时装的字节数,自2.6.11内核后,容量为64k。管道满了就会导致写操作产生阻塞。
管道缓冲区大小:由PIPE_BUF指定,指的是保证管道写操作为原子操作的最大值,如果一次写入的内容超过这个值,那么这次的写操作就不是原子的。什么意思呢?就是指,可能存在多个进程写同一个管道,如果一次写入的字节数大于缓冲区大小,则可能会出现A进程写入的内容中插入了B进程写入的内容。
下面是出现这种情况的代码:
1 #include <sys/types.h> 2 #include <sys/stat.h> 3 #include <stdio.h> 4 #include <fcntl.h> 5 #include <signal.h> 6 #include <unistd.h> 7 #include <stdlib.h> 8 #include <string.h> 9 10 #define MAXLINE 4096+100 11 12 int main(void) 13 { 14 int n; 15 int fd[2]; 16 pid_t pid; 17 char line[MAXLINE]; 18 19 if (pipe(fd) < 0) 20 { 21 perror("pipe error"); 22 } 23 24 if ((pid = fork()) < 0) 25 { 26 perror("fork error"); 27 } 28 else if (pid > 0) /* parent */ 29 { 30 close(fd[1]); 31 while ( 1 ) 32 { 33 n = read(fd[0], line, MAXLINE); 34 write(STDOUT_FILENO, line, n); 35 write(STDOUT_FILENO, " ", 3); 36 } 37 } 38 else /* child */ 39 { 40 if ((pid = fork()) < 0) 41 { 42 perror("fork error"); 43 } 44 else if (pid > 0) 45 { 46 close(fd[0]); 47 48 while (1) 49 { 50 memset(line, 'a',MAXLINE); 51 write(fd[1], line, MAXLINE); 52 } 53 } 54 else 55 { 56 close(fd[0]); 57 58 while ( 1 ) 59 { 60 memset(line, 'b',MAXLINE); 61 write(fd[1], line, MAXLINE); 62 } 63 } 64 } 65 66 exit(0); 67 }