四十六、进程间通信——管道的分类与读写
46.1 管道介绍
46.1.1 管道通信
- 管道是针对于本地计算机的两个进程之间的通信而设计的通信方法,建立管道后,实际获得两个文件描述符:一个用于读取而另一个用于写入
- 最常见的 IPC 机制,通过 pipe 系统调用
- 管道是单工的,数据只能向一个方向流动,需要双向通信时,需要建立起两个管道
- 数据的读出和写入:
- 一个进程向管道中写的内容被管道另一端的进程读出。
- 写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据
- 管道实际上就是建立在内核区域的一块缓存
46.1.2 管道分类
- 匿名管道:
- 在关系进程中进行(父进程和子进程、兄弟进程之间)
- 由 pipe 系统调用,管道由父进程建立
- 管道位于内核空间,其实是一块缓存
- 命名管道(FIFO)
- 两个没有任何关系的进程之间通信可通过命名管道进行数据传输,本质是内核中的一块缓存,另在文件系统中以一个特殊的设备文件(管道文件)存在
- 通过系统调用 mkfifo 创建
46.1.3 管道创建
1 #include <unistd.h> 2 int pipe(int fd[2]);
- 函数参数:两个文件描述符数组
- fd[0]:为 pipe 的读端
- fd[1]:为 pipe 的写端
- fd[0] 用于读取管道,fd[1] 用于写入管道
- 返回值:成功返回 0;出错返回 -1
案例
46.1.4 管道读写
- 管道主要用于不同进程间通信。实际上,通常先创建一个管道,再通过 fork 函数创建一个子进程
46.2 例子
46.2.1 管道应用
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <unistd.h> 5 #include <sys/types.h> 6 #include <sys/wait.h> 7 8 /** 9 * 父进程通过管道传输两个数据给子进程 10 * 由子进程负责从管道中读取并输出 11 */ 12 int main(void) 13 { 14 int fd[2]; 15 16 /** 创建管道 */ 17 if(pipe(fd) < 0){ 18 perror("pipe error"); 19 exit(1); 20 } 21 22 pid_t pid; 23 if((pid = fork()) < 0){ 24 perror("fork error"); 25 exit(1); 26 } 27 else if(pid > 0) { ///< 父进程 28 close(fd[0]); ///< 父进程用来写入数据 29 int start = 1, end = 100; 30 if((write(fd[1], &start, sizeof(int))) != sizeof(int)) { 31 perror("write error"); 32 exit(1); 33 } 34 35 if((write(fd[1], &end, sizeof(int))) != sizeof(int)) { 36 perror("write error"); 37 exit(1); 38 } 39 40 close(fd[1]); 41 wait(0); 42 } 43 else { ///< 子进程 44 close(fd[1]); ///< 子进程用来读取数据 45 int start, end; 46 if(read(fd[0], &start, sizeof(int)) < 0){ 47 perror("read error"); 48 exit(1); 49 } 50 51 if(read(fd[0], &end, sizeof(int)) < 0){ 52 perror("read error"); 53 exit(1); 54 } 55 56 close(fd[0]); 57 printf("child process read start: %d, end %d\n", start, end); 58 } 59 60 return 0; 61 }
编译运行:
46.2.2 在管道系统中使用 excu 函数
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/wait.h> 5 #include <sys/types.h> 6 7 char *cmd1[3] = {"/bin/cat", "/etc/passwd", NULL}; 8 char *cmd2[3] = {"/bin/grep", "root", NULL}; 9 10 int main(void) 11 { 12 int fd[2]; 13 if(pipe(fd) < 0){ 14 perror("pipe error"); 15 exit(1); 16 } 17 18 int i = 0; 19 pid_t pid; 20 for(; i < 2; i++){ 21 pid = fork(); 22 if(pid < 0){ 23 perror("fork error"); 24 exit(1); 25 } 26 else if(pid == 0){ 27 if(i == 0){ /** 第一个子进程负责往管道写入数据 */ 28 /** 关闭读端 */ 29 close(fd[0]); 30 31 /** 将标准输出重定向到管道的写端 32 * 下面命令的执行的结果会写入到管道中,而不是输出到屏幕 */ 33 if(dup2(fd[1], STDOUT_FILENO) != STDOUT_FILENO) 34 { 35 perror("dup2 error"); 36 } 37 38 close(fd[1]); 39 40 /** 调用 exec 函数执行 cat 命令 */ 41 if(execvp(cmd1[0], cmd1) < 0){ 42 perror("execvp error"); 43 exit(1); 44 } 45 break; 46 } 47 48 if(i == 1){/** 第二个子进程负责从管道读取数据 */ 49 /** 关闭写端 */ 50 close(fd[1]); 51 52 /** 将标准输入重定向到管道的读端 */ 53 if(dup2(fd[0], STDIN_FILENO) != STDIN_FILENO){ 54 perror("dup2 error"); 55 } 56 57 close(fd[0]); 58 59 /** 调用 exec 函数执行 grep 命令 */ 60 if(execvp(cmd2[0], cmd2) < 0){ 61 perror("execvp error"); 62 exit(1); 63 } 64 65 break; 66 } 67 } 68 else { 69 if(i == 1){ 70 /** 父进程要等到子进程全部创建完毕才去回收 */ 71 close(fd[0]); 72 close(fd[1]); 73 wait(0); 74 wait(0); 75 } 76 77 } 78 } 79 return 0; 80 }
执行结果如下: