Linux进程间通信(IPC) 需求1: 实现: 用兄弟进程以及管道通信(无名)来实现命令 ls | wc -l
ls | wc -l命令使用了管道,将ls命令的输出作为wc命令的输入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <unistd.h> #include <stdio.h> #include <sys/wait.h> int main (int argc, char * argv[]) { int i; int fd[2 ]; int res = pipe (fd); if (res == -1 )perror ("pipe error" ); for (i = 0 ; i < 2 ; i++) { pid_t pid; pid = fork(); if (pid == 0 )break ; } if (i == 2 ){ close (fd[0 ]); close (fd[1 ]); wait (NULL ); wait (NULL ); }else if (i == 0 ){ close (fd[0 ]); dup2 (fd[1 ], STDOUT_FILENO); execlp ("ls" , "ls" , NULL ); } else if (i == 1 ) { close (fd[1 ]); dup2 (fd[0 ], STDIN_FILENO); execlp ("wc" , "wc" , "-l" , NULL ); } return 0 ; }
补充: linux中父子进程的文件描述符是共享的。因此实现父子进程或兄弟进程通信时,可以使用pipe(无名管道),相当于父进程用fork的方式,将管道的读端和写端传给了子进程。但只能用于有血缘关系的进程之间的通信。
在使用时需要关闭进程的读端或写端,确保有且仅有一个进程持有管道的写端,另一进程持有管道的读端。否则可能会产生阻塞。
管道机制: 读管道:
管道有数据:read 返回实际读到的字节数。
管道无数据:
1)无写端,read 返回 0 (类似读到文件尾)
2)有写端,read 阻塞等待。
写管道:
1) 管道已满, 阻塞等待
2) 管道未满, 返回写出的字节个数。
分析 从上述机制不难分析,当有非写入程序持有管道的写入端的时候,可能会造成后续读端一直阻塞。因此要通过关闭不同进程的读端和写端来明确管道的方向,防止阻塞。
需求2: 采用管道来实现无血缘关系的进程之间的通信。
思路: linux中”一切皆文件“,管道也不例外。pipe()函数所创建的管道没有名称,一般被称为无名管道,但这种管道只能用于有血缘关系的进程间使用。而另一种管道——FIFO,常被称为命名管道来区分pipe,通过FIFO,无血缘关系的进程也可以进行通信。
FIFO是linux基础文件类型中的一种,在目录中可以看到,但在磁盘上并没有数据块,并不占用磁盘空间,仅仅用来标识通往内核的一条通道,不同的进程通过打开这个文件,来进行read和write。实际上是在读写内核通道,从而实现了进程间通信。
命名管道的创建方式
命令:
库函数:
1 int mkfifo (const char * pathname, mode_t mode) ;
成功返回0,失败返回-1
实现: 写入进程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <unistd.h> #include <stdio.h> #include <fcntl.h> #include <string.h> int main (int argc, char * argv[]) { char buf[4096 ]; printf ("I'm write\n" ); if (argc < 2 ) { printf ("Enter like this: ./xxx.out fifoname\n" ); return -1 ; } int fd = open (argv[1 ], O_WRONLY); int i = 0 ; while (i < 30 ) { sprintf (buf, "this is the %dth message\n" ,i++); write (fd, buf, strlen (buf)); sleep (1 ); } close (fd); }
读出进程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <fcntl.h> #include <unistd.h> #include <stdio.h> int main (int argc, char * argv[]) { printf ("I'm read\n" ); if (argc < 2 ) { printf ("enter like this: ./xxx.out fifoname\n" ); return -1 ; } int fd = open (argv[1 ], O_RDONLY); char buf[4096 ]; int len; while (1 ) { len = read (fd, buf, sizeof (buf)); write (STDOUT_FILENO, buf, len); sleep (1 ); } close (fd); return 0 ; }
效果实例
补充:
sprintf函数可以将字符串写入到指定的缓冲区中,写入的内容为“字符串+/0”,每次写入结束后都会确保末尾为/0,因此在每次写入前也可以不清除缓冲区中已有的字符串。
在打开管道的时候应注意权限的控制,读入端O_RDONLY,写入端O_WRONLY,权限设置错误可能会造成open函数阻塞。