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,此后父子进程之间就可该管 ...
随机推荐
- Java中list集合深复制
import org.apache.commons.collections.CollectionUtils; import java.util.ArrayList; import java.util. ...
- 万节点规模云服务的 SRE 能力建设
简介: 随着越来越多企业以容器作为系统底座,那么阿里云的云服务又是如何进行SRE规划呢?下文将由资深SRE工程师拆解2 万节点规模云服务背后的 SRE 能力建设,立即点击观看! 作者:宋傲(凡星) ...
- 揭秘sealer背后实现整个集群一键交付的奥秘 | 龙蜥技术
简介:解读集群镜像"开箱即用"神器--sealer! 编者按:集群镜像把整个集群看成一台服务器,把 k8s 看成云操作系统,实现整个集群的镜像化打包和交付,为企业级软件提供一种& ...
- 收藏!这些IDE使用技巧,你都知道吗
简介: 欲善其事,先利其器.对于研发同学,在日常的开发工作中,我们与之打交道最多的便是编程的IDE.能否高效和灵活的使用IDE,将对我们的工作效率起着举足轻重的作用. 一 .背景 1 .目的 欲善其事 ...
- [Py] Failed to import pydot. You must install pydot and graphviz for `pydotprint` to work
当通过常规命令安装 pip install pydot 和 brew install graphviz 之后,在代码中 import pydot 依旧不生效. 比如:在 tensorflow 使用 t ...
- [Py] Python json str 字符串转为对象 (字典)
import json json = '{"code": 0}' # Deserialize ``s`` (a ``str``, ``bytes`` or ``bytearray` ...
- WPF 自己封装 Skia 差量绘制控件
使用 Skia 能做到在多个不同的平台使用相同的一套 API 绘制出相同界面效果的图片,可以将图片绘制到应用程序的渲染显示里面.在 WPF 中最稳的方法就是通过 WriteableBitmap 作为承 ...
- C# 采集知网
采集知网 WebClient /// <summary> /// 支持 Session 和 Cookie 的 WebClient. /// </summary> public ...
- vue-cli快速搭建项目的几个文件(一)
===========app.vue文件============= <template> <div id="app"> <router ...
- Competition Set - 模拟赛 I
HNOI2017 Day2 2023-06-10 注:Day2T2换为BJOI2017Day2T1,以匹配学习进度 A.大佬 B.抛硬币 C.喷式水战改 A 大佬 你需要用 \(n\) 天挑战一名大佬 ...