Linux进程间通信-管道(pipe)
本系列文章主要是学习记录Linux下进程间通信的方式。
常用的进程间通信方式:管道、FIFO、消息队列、信号量以及共享存储。
参考文档:《UNIX环境高级编程(第三版)》
参考视频:Linux进程通信 推荐看看,老师讲得很不错
Linux核心版本:2.6.32-431.el6.x86_64
注:本文档只是简单介绍IPC,更详细的内容请查看参考文档和相应视频。
本文介绍利用管道进行进程间的通信。
1 简介
管道是最古老的一种方式,局限性:
- 半双工方式,数据只能在一个方向上流动;
- 只能在具有公共祖先的两个进程间使用。
2 函数接口
1 #include <unistd.h>
2 int pipe(int pipefd[2]);
3 说明:创建一个pipe
4 返回值:成功返回0,出错返回-1
5 参数[out]:fd保存返回的两个文件描述符,fd[0]为读而打开,fd[1]为写而打开。fd[1]的输出是fd[0]的输入。
3 通信模型
通信模型一:进程先调用pipe,接着调用fork,从而创建从父进程到子进程的IPC通道。

通信模型二:从父进程到子进程的通道。父进程关闭管道的读端(fd[0]),子进程关闭写端(fd[1])。

当管道一端被关闭后,以下两条规则起作用:
当读(read)一个写端已被关闭的管道时,在所有数据都被读取后,read返回0,表示文件结束。
如果写(write)一个读端已被关闭的管道,则产生信号SIGPIPE。
4 读写特性
1)可通过打开两个管道来创建一个双向的管道;
2)管道是阻塞性的,当进程从管道中读取数据,若没有数据进程会阻塞;
3)当一个进程往管道中不断的写入数据但是没有进程去读取数据,此时只要管道没有满是可以的,但若管道满的则会报错。
5 测试代码
(1)实例1
创建一个从父进程到子进程的管道,并且父进程通过该管道向子进程传送数据。
1 #include <stdio.h>
2 #include <unistd.h>
3
4 #define MAXLINE 512
5
6 int main(void)
7 {
8 int n;
9 int fd[2];
10 pid_t pid;
11 char line[MAXLINE];
12
13 if (pipe(fd) < 0) { //创建管道
14 perror("pipe error!");
15 return -1;
16 }
17 if ((pid = fork()) < 0) { //创建子进程
18 perror("fork error!");
19 return -1;
20 } else if (pid > 0) { //父进程
21 close(fd[0]); //父进程关闭读管道
22 write(fd[1], "hello world\n", 12); //父进程向管道中写入数据
23 close(fd[1]);
24 wait(0); //等待子进程结束
25 } else { //子进程
26 close(fd[1]); //子进程关闭写管道
27 n = read(fd[0], line, MAXLINE); //子进程从管道中读取数据
28 write(STDOUT_FILENO, line, n); //标准输出
29 close(fd[0]);
30 }
31
32 return 0;
33 }
(2)实例2
使用pipe实现类似于:cat /etc/passwd | grep root这个命令。

1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4
5 char *cmd1[3] = {"/bin/cat", "/etc/passwd", NULL};
6 char *cmd2[3] = {"/bin/grep", "root", NULL};
7
8 int main(void)
9 {
10 int fd[2];
11 int i = 0;
12 pid_t pid;
13
14 if (pipe(fd) < 0) {
15 perror("pipe error");
16 exit(1);
17 }
18
19 for (i = 0; i < 2; i++) {
20 pid = fork();
21 if (pid < 0) {
22 perror("fork error");
23 exit(1);
24 } else if (pid == 0) {
25 if (i == 0) { //第一个子进程
26 //负责往管道写入数据
27 close(fd[0]); //关闭读端
28 //cat命令执行结果是标准输出,需要将标准输出重定向到管道写端
29 //下面命令执行的结果会写入到管道中,而不是输出到屏幕
30 if (dup2(fd[1], STDOUT_FILENO) != STDOUT_FILENO) {
31 perror("dup2 error");
32 exit(1);
33 }
34 close(fd[1]); //已经复制了一份,原来的可以关闭
35 //调用exce函数执行cat命令
36 if (execvp(cmd1[0], cmd1) < 0) {
37 perror("execvp error");
38 exit(1);
39 }
40 break;
41 }
42 if (i == 1) { //第二个子进程
43 //负责从管道读取数据
44 close(fd[1]); //关闭写端
45 //grep命令默认读取的内容来源于标准输入
46 //需要将标准输入重定向到管道的读端
47 //下面命令执行时从管道的读端读取内容,而不是从标准输入读取
48 if (dup2(fd[0], STDIN_FILENO) != STDIN_FILENO) {
49 perror("dup2 error");
50 exit(1);
51 }
52 close(fd[0]);
53 //调用exce函数执行grep命令
54 if (execvp(cmd2[0], cmd2) < 0) {
55 perror("execvp error");
56 exit(1);
57 }
58 break;
59 }
60 } else { //父进程,仅用于创建子进程
61 //等待子进程创建并回收
62 if (i == 1) {
63 //等待子进程全部创建完毕,才回收
64 close(fd[0]);
65 close(fd[1]);
66 wait(0);
67 wait(0);
68 }
69 }
70 }
71
72 return 0;
73 }
(3)实例3
使用pipe实现一个协同进程。

创建两个管道,父进程向管道1中写入数据,并从管道2中读取子进程计算出的结果值;
子进程从管道1中读取数据,并调用add程序进行累加,将累加的结果写入到管道2中。
add程序实现代码:
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4
5 int main(void)
6 {
7 int x, y;
8
9 if (read(STDIN_FILENO, &x, sizeof(int)) < 0) {
10 perror("read error");
11 }
12 if (read(STDIN_FILENO, &y, sizeof(int)) < 0) {
13 perror("read error");
14 }
15
16 int result = x + y;
17 if (write(STDOUT_FILENO, &result, sizeof(int)) < sizeof(int)) {
18 perror("write error");
19 }
20
21 return 0;
22 }
协同进程实现代码:
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4
5 int main(void)
6 {
7 int fda[2], fdb[2];
8
9 if ((pipe(fda) < 0) || (pipe(fdb) < 0)) {
10 perror("pipe error");
11 exit(1);
12 }
13
14 pid_t pid;
15 pid = fork();
16 if (pid < 0) { //子进程
17 perror("fork error");
18 exit(1);
19 } else if (pid == 0) {
20 //1、子进程负责从管道a中读取父进程写入的累加参数x和y
21 //2、通过exec函数调用/bin/add程序进行累加
22 //3、将累加结果写入到管道b
23 close(fda[1]);
24 close(fdb[0]);
25 //将标准输入重定向到管道a的读端
26 //add程序中将从管道a中读取累加参数x和y
27 if (dup2(fda[0], STDIN_FILENO) != STDIN_FILENO) {
28 perror("dup2 error");
29 }
30 //将标准输出重定向到管道b的写端
31 //add程序累加后的结果会写入到管道b
32 if (dup2(fdb[1], STDOUT_FILENO) != STDOUT_FILENO) {
33 perror("dup2 error");
34 }
35 close(fda[0]);
36 close(fdb[1]);
37 if (execlp("bin/add", "bin/add", NULL) < 0) {
38 perror("execlp error");
39 exit(1);
40 }
41 } else { //父进程
42 //1、从标准输入上读取累加参数x和y
43 //2、将x和y写入管道a
44 //3、从管道b中读取累加的结果并输出
45 close(fda[0]);
46 close(fdb[1]);
47 //1
48 int x, y;
49 printf("please input x and y: ");
50 scanf("%d %d", &x, &y);
51 //2
52 if (write(fda[1], &x, sizeof(int)) != sizeof(int)) {
53 perror("write error");
54 }
55 if (write(fda[1], &y, sizeof(int)) != sizeof(int)) {
56 perror("write error");
57 }
58 //3
59 int result = 0;
60 if (read(fdb[0], &result, sizeof(int)) != sizeof(int)) { //阻塞式读写
61 perror("read error");
62 }
63 printf("add result is %d\n", result);
64 close(fda[1]);
65 close(fdb[0]);
66 wait(0);
67 }
68
69
70 return 0;
71 }
测试结果:
[root@192 ipc]# gcc -o bin/add add.c
[root@192 ipc]# gcc -o bin/co_pro c_process.c
[root@192 ipc]# ./bin/co_pro
please input x and y: 12 23
add result is 35
(4)案例4
实现一个不完整管道:当读一个写端已被关闭的管道时,在所有数据被读取后,read返回0,以表示到达了文件尾部。
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4
5 /*
6 * 不完整管道:读取一个写端已经关闭的管道
7 */
8
9 int main(void)
10 {
11 int fd[2];
12
13 if (pipe(fd) < 0) {
14 perror("pipe error");
15 exit(1);
16 }
17 pid_t pid;
18 if ((pid = fork()) < 0) {
19 perror("fork error");
20 exit(1);
21 } else if (pid > 0) { //父进程
22 //父进程从不完整管道(写端关闭)中读取数据
23 sleep(5); //等子进程将管道写端关闭
24 close(fd[1]);
25 while (1) {
26 char c;
27 if (read(fd[0], &c, 1) == 0) {
28 printf("\nwrite-end of pipe closed\n");
29 break;
30 } else {
31 printf("%c", c);
32 }
33 }
34 } else { //子进程
35 // 子进程负责将数据写入管道
36 close(fd[0]);
37 char *s = "1234";
38 write(fd[1], s, sizeof(s));
39 close(fd[1]);
40 }
41
42 return 0;
43 }
(5)案例5
实现一个不完整管道:当写一个读端被关闭的信号,则产生信号SIGPIPE,如果忽略该信号或捕捉该信号并从处理程序返回,则write返回-1,同时errno设置为EPIPE。
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <errno.h>
6 #include <signal.h>
7
8 /*
9 * 不完整管道:写入一个读端已经被关闭的管道
10 */
11
12 void sig_handler(int signo)
13 {
14 if (signo == SIGPIPE) {
15 printf("SIGPIPE occured\n");
16 }
17 }
18
19 int main(void)
20 {
21 int fd[2];
22
23 if (pipe(fd) < 0) {
24 perror("pipe error");
25 exit(0);
26 }
27
28 pid_t pid;
29 if ((pid = fork()) < 0) {
30 perror("fork error");
31 } else if (pid > 0) { //父进程
32 //父进程负责将数据写入到不完整管道(读端关闭)中
33 sleep(5);
34 close(fd[0]);
35 if (signal(SIGPIPE, sig_handler) == SIG_ERR) {
36 perror("signal sigpipe error");
37 exit(1);
38 }
39 char *s = "1234";
40 if (write(fd[1], s, sizeof(s)) != sizeof(s)) {
41 fprintf(stderr, "%s, %s\n", strerror(errno), (errno == EPIPE) ? "EPIPE" : ", unkown");
42 }
43 close(fd[1]);
44 wait(0);
45 } else { //子进程
46 //关闭管道的读端
47 close(fd[0]);
48 close(fd[1]);
49 }
50
51 return 0;
52 }
6 标准库中的管道操作
函数实现的操作:创建一个管道,fork一个子进程,关闭未使用的管道端,执行一个shell运行命令,然后等待命令终止。
(1)函数原型
1 #include <stdio.h>
2 FILE *popen(const char *command, const char *type);
3 返回值:成功返回文件指针,出错返回NULL。
4 参数command:命令的路径。
5 参数type:读写特性,”r”或”w”
6 int pclose(FILE *stream);
函数popen先执行fork,然后调用exec执行command,并且返回一个标准I/O文件指针。
如果type是“r”,则文件指针连接到command的标准输出。
如果type是"w",则文件指针连接到command的标准输入。
(2)popen内部原理

(3)实例
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 int main(void)
5 {
6 FILE *fp;
7
8 //命令执行的结果放置到fp指向的结构体缓存中
9 fp = popen("cat /etc/passwd", "r");
10 char buf[512] = {0};
11 while (fgets(buf, sizeof(buf), fp) != NULL) {
12 printf("%s", buf);
13 }
14 pclose(fp);
15
16 printf("----------------------------------\n");
17 //为wr命令提供统计的数据
18 fp = popen("wc -l", "w");
19 fprintf(fp, "1\n2\n3\n");
20 pclose(fp);
21
22 return 0;
23 }
Linux进程间通信-管道(pipe)的更多相关文章
- Linux进程间通信 -- 管道(pipe)
前言 进程是一个独立的资源管理单元,不同进程间的资源是独立的,不能在一个进程中访问另一个进程的用户空间和内存空间.但是,进程不是孤立的,不同进程之间需要信息的交互和状态的传递,因此需要进程间数据 ...
- Linux进程间通信—管道
Linux下的进程通信手段基本上是从UNIX平台上的进程通信手段继承而来的.而对UNIX发展做出重大贡献的两大主力AT&T的贝尔实验室及BSD(加州大学伯克利分校的伯克利软件发布中心)在进程间 ...
- Linux进程间通信:管道,信号量,消息队列,信号,共享内存,套接字
Linux下的进程通信手段基本上是从UNIX平台上的进程通信手段继承而来的.而对UNIX发展做出重大贡献的两大主力AT&T的贝尔实验室及BSD(加州大学伯克利分校的伯克利软件发布中心)在进程间 ...
- linux中管道(pipe)一谈
/*********************************************** 管道(pipe)是Linux上进程间通信的一种方式,其是半双工(数据流只能在一个方向上流动(还需要经过 ...
- Linux进程间通信-管道深入理解(转)
原文地址:https://www.linuxidc.com/Linux/2018-04/151680.htm Linux进程通信系列文章将详细介绍各种通信方式的机制和区别 1.进程间通信 每个进程各自 ...
- Linux进程间通信---管道和有名管道
一.管道 管道:管道是一种半双工的通信方式,数据只能单方向流动,而且只能在具有亲缘关系的进程间使用,因为管道 传递数据的单向性,管道又称为半双工管道.进程的亲缘关系通常是指父子进程关系. 管道的特点决 ...
- 详解linux进程间通信-管道 popen函数 dup2函数
前言:进程之间交换信息的唯一方法是经由f o r k或e x e c传送打开文件,或通过文件系统.本章将说明进程之间相互通信的其他技术-I P C(InterProcess Communication ...
- linux 进程间通信之pipe
在实际开发过程中,程序员必须让拥有依赖关系的进程集协调,这样才能达到进程的共同目标. 每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内 ...
- Linux 进程间通信(管道、共享内存、消息队列、信号量)
进程通信 : 不同进程之间传播或交换信息 为什么要进程通信呢? 协同运行,项目模块化 通信原理 : 给多个进程提供一个都能访问到的缓冲区. 根据使用场景,我们能划分为以下几种通信 ...
- linux进程间通信-管道
一 管道的局限性 管道有两个局限性:(1)他是半双工(即数据只能在一个方向上流动).(2)它只能在具有公共祖先的进程之间使用.一个管道由一个进程创建,然后该 进程调用fork,此后父子进程之间就可该管 ...
随机推荐
- docker搭建kafka集群实践
前言 本文主要介绍了如何通过docker搭建一个可以用于生产环境的kafka集群. kafka集群使用了3个节点,依赖zookeeper进行协调,所以会同时搭建一套3节点的zookeeper集群. 准 ...
- 推荐一个计算Grad-CAM的Python库
前言 类激活图CAM(class activation mapping)用于可视化深度学习模型的感兴趣区域,增加了神经网络的可解释性.现在常用Grad-CAM可视化,Grad-CAM基于梯度计算激活图 ...
- vue项目中嵌入软键盘(中文/英文)
键盘效果是这样,样式可以自己调整.gittee地址:https://gitee.com/houxianzhou/VirtualKeyboard.git步骤1 安装使用jQuery npm instal ...
- ElasticSearch 7.7 + Kibana的部署
ElasticSearch目前最新版是7.7.0,其中部署的细节和之前的6.x有很多的不同,所以这里单独拉出来写一下,希望对用7.x的童鞋有一些帮助,然后部署完ES后配套的kibana也是7.7.0, ...
- 《Effective C#》系列之(一)——异常处理与资源管理
请注意,<Effective C#>中的异常处理与资源管理部分实际上是第四章的内容.以下是关于该章节的详细解释. 第四章:异常处理与资源管理 一. 了解异常处理机制 异常处理机制使程序员能 ...
- 剑指offer29(Java)-顺时针打印矩阵(简单)
题目: 输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字. 示例 1: 输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]输出:[1,2,3,6,9,8,7,4,5 ...
- 进一步释放技术红利,阿里云推出全新内存增强型实例re6,性能提升30%
5月7日,国内最大云计算厂商阿里云宣布推出全新一代内存增强型实例,提供1:14.8超大内存比内存容量,满足内存型数据库如SAP HANA.Redis等应用,充分释放技术红利,帮助线下企业快速上云,完成 ...
- KubeVela 1.3 发布:开箱即用的可视化应用交付平台,引入插件生态、权限认证、版本化等企业级新特性
简介:得益于 KubeVela 社区上百位开发者的参与和 30 多位核心贡献者的 500 多次代码提交, KubeVela 1.3 版本正式发布.相较于三个月前发布的 v1.2 版本[1],新版本在 ...
- 阿里云PolarDB开源数据库社区与 Tapdata 联合共建开放数据技术生态
简介:近日,阿里云PolarDB开源数据库社区宣布将与 Tapdata 联合共建开放数据技术生态. 近日,阿里云PolarDB开源数据库社区宣布将与 Tapdata 联合共建开放数据技术生态.在此之 ...
- 使用 rocketmq-spring-boot-starter 来配置、发送和消费 RocketMQ 消息
简介: 本文将 rocktmq-spring-boot 的设计实现做一个简单的介绍,读者可以通过本文了解将 RocketMQ Client 端集成为 spring-boot-starter 框架的开发 ...