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,此后父子进程之间就可该管 ...
随机推荐
- Redis 性能优化实战
Redis 作为内存数据库,其性能表现非常出色,单机 OPS 很容易达到 10万以上,这主要得益于其高效的内存数据结构.单线程无锁设计.IO 多路复用等技术实现.但是在线上生产环境的使用中,我们仍然会 ...
- CF1481D AB Graph 题解
CF1481D AB Graph 题解 [思路] 首先有几个显而易见的东西. 如果存在两个点,他们之间的两条边字母相同,那么一定有解(在两个点之间跳.) 否则,这张图的邻接矩阵一定长成这样: * a ...
- 【知识点】如何快速开发、部署 Serverless 应用?
简介: 本文将详细介绍如何开发和部署 Serverless 应用,并通过阿里云函数计算控制台与开发者工具 Serverless Devs 进行应用的初始化.部署:最后分享应用的调试,通过科学发布.可观 ...
- Serverless 在阿里云函数计算中的实践
简介: 近日,阿里云 aPaaS&Serverless 前端技术专家袁坤在 CSDN 云原生 meetup 长沙站分享了 Serverless 在阿里云函数计算 FC 的实践. 作者:CSDN ...
- Java 定时任务技术趋势
简介:定时任务是每个业务常见的需求,比如每分钟扫描超时支付的订单,每小时清理一次数据库历史数据,每天统计前一天的数据并生成报表等等. 作者:黄晓萌(学仁) Java 中自带的解决方案 使用 Time ...
- 重磅发布:微服务引擎 MSE 专业版
简介: 性能提升 10 倍,更高的 SLA 保障,新用户限时抢购 8 折资源包. 微服务引擎 MSE 专业版发布,支持 Nacos 2.0 ,相比基础版,专业版具有更高的 SLA 保障,性能提升十倍, ...
- 移动云正式发布基于龙蜥 Anolis OS 的 BC-Linux V8.2 通用版操作系统
简介: 2020年12月CentOS项目组宣布CentOS 8将于2021年12月31日结束支持,这意味着从2022年开始,使用CentOS 8的用户,将无法得到来自官方的新硬件支持.bug修复和安全 ...
- [FAQ] Large files detected. You may want to try Git Large File Storage
Git 提交文件大于 100M 时提示需要使用 Git LFS. Ubuntu 安装示例: $ curl -s https://packagecloud.io/install/repositories ...
- 通过 KoP 将 Kafka 应用迁移到 Pulsar
通过 KoP 将 Kafka 应用迁移到 Pulsar 版权声明:原文出自 https://github.com/streamnative/kop ,由 Redisant 进行整理和翻译 目录 通过 ...
- k8s问题解决
问题1: 问题描述:k8s中Terminating状态pod不能删除 [root@master ~]# kubectl get pods -n ms NAME READY STATUS RESTART ...