文章目录

  • 管道符[|]
  • 创建匿名管道的pipe函数
  • 从内核角度深入理解管道
  • 代码验证pipe函数
  • 管道和子进程的先后创建顺序
  • 代码实现父子进程的通信
  • 管道的特性

前言

为什么需要进程间通信呢?

每一个进程的数据都是存储在物理内存当中的,进程通过各自的进程虚拟地址空间进行访问,访问的时候,通过各自的页表映射关系,访问到物理内存。
从进程的角度看,每个进程都认为自己有4G的空间,物理内存当中怎么存储,进程并不清楚,这也保证了进程的独立性。
因为进程的独立性,所以进程之间不能进行数据交换,进程间通信就是为了让进程和进程之间交换数据的。
进程间通信方式:管道、共享内存、消息队列、网络。

管道符[|]

ps aux | grep test

我们通过上面这个命令,来理解一下管道符,管道符左侧的命令是列举当前Linux操作系统当中的进程信息,右侧命令是过滤存在test字符串的项,ps aux 的返回结果,通过管道,传输给grep进程,grep进程在ps aux的结果当中过滤存在test的字符串,这样,独立的ps aux进程就和独立的grep进程通过管道进行了通信。

管道的本质:管道在内核当中就是一块缓冲区,供进程进行读写,交换数据。

创建匿名管道的pipe函数

pipe函数原型:

int pipe(int pipefd[2];

调用该函数创建一个匿名管道

参数:是一个数组,有两个元素。

当我们调用pipe函数的时候,我们就在内核当中创建了一块缓冲区(管道),程序员需要往这个缓冲区中读写,于是pipe函数就给我们提供了读写两端,pipefd[0]是管道的读端,pipefd[1]是管道的写端,pipe函数通过参数告诉我们它创建的管道的读写两端是什么,所以函数参数为出参,程序员在创建管道之前,肯定是不知道所创建的管道的读写两端的。

参数为出参,也就是pipefd的值是由pipe函数进行填充的,调用者进行使用。

pipefd[0]是管道的读端,pipefd[1]是管道的写端,这两个元素保存的内容是文件描述符,也就是我们调用了pipe函数,该函数给我们创建了一个匿名管道,pipe函数给我们返回一个数组,该数组有两个元素,这两个元素的类型就是文件描述符,程序员通过操作这两个文件描述符,对管道进行读写。

返回值:创建成功返回0,创建失败返回-1.

从内核角度深入理解管道

代码验证pipe函数

我们要通过代码验证以下三个方面的内容:

  • 验证pipe函数的出参
  • 验证fd[0]、fd[1]是文件描述符
  • 通过fd[0]、fd[1]是不是可以进行读写
 1 #include <stdio.h>2 #include <unistd.h>3 #include <string.h>4 int main(){5   int fd[2];6   printf("fd[0] = %d, fd[1] = %d.\n",fd[0],fd[1]);7   int ret = pipe(fd);8   if(ret == -1){9     perror("pipe");10     return 0;11   }12   printf("fd[0] = %d, fd[1] = %d.\n",fd[0],fd[1]);13 14   const char* lp = "hello world";15   write(fd[1],lp,strlen(lp));16 17   char buf[1024] = {0};18   read(fd[0],buf,sizeof(buf)-1);19 20   printf("read buf : %s\n",buf);21   return 0;                                                                    22 }          

执行结果:

[jxy@VM-4-2-centos pipe_test]$ ./pipe_test
fd[0] = 4196224, fd[1] = 0.
fd[0] = 3, fd[1] = 4.
read buf : hello world

我们给他加上一个睡眠,去 /proc/[pid]/fd 下面看看是不是多了两个描述符,并且该文件描述符是不是和fd[0]、fd[1]对应。

管道和子进程的先后创建顺序

父子进程要通过匿名管道进行通信,核心点在于父子进程都要能够读写管道,也就意味着父子进程都有管道两端的文件描述符。

那如果要实现父子进程匿名管道的通信,到底应该先创建管道还是应该先创建子进程呢?

我们假设先创建子进程再创建匿名管道:

子进程拷贝父进程的PCB,那子进程的文件描述符表和父进程相同,如果先创建子进程再创建管道,那子进程就得不到父进程创建的匿名管道的读写两端,最后就是这样的结果:

子进程的文件描述符表里面没有匿名管道的读写两端的文件描述符,所以父子进程此时就无法正常通过匿名管道进行通信。

我们再假设先创建匿名管道再创建子进程:

最后子进程拷贝父进程的PCB,得到的就是这样一个结果:

在先创建管道再创建子进程的情况下,子进程拷贝得到的文件描述符表就包含了父进程创建的读写两端的文件描述符。这种情况下,父子进程就可以通过匿名管道进行通信了。
结论:一定要先创建匿名管道,再创建子进程,否则,子进程当中就没有匿名管道读写两端的文件描述符。

代码实现父子进程的通信

我们写一段代码,目标即使实现父子进程的通信,代码逻辑就是父进程先创建匿名管道,父进程再创建子进程,父进程写,子进程读,代码如下:

#include <string.h>4 int main(){5   int fd[2];6   int ret = pipe(fd);7   //创建管道8   if(ret == -1){9     perror("pipe");10     return 0;11   }12 13   ret = fork();14   //创建子进程15   if(ret < 0){16     perror("fork");17     return 0;18   }else if(ret == 0){19     //child20     char buf[1024] = {0};21     read(fd[0],buf,sizeof(buf)-1);22     //子进程读23     printf("I am child,read buf :%s.\n",buf);24   }else{25     //father26     const char* lp = "I am father,hello child";                                27     write(fd[1],lp,strlen(lp));                28     //父进程写                 29   }           30   return 0;31 }

执行结果:

[jxy@VM-4-2-centos pipe_comm]$ ./pipe_comm
I am child,read buf :I am father,hello child.

通过这个执行结果,不难看出来,我们的父子进程通过匿名管道进行了数据交换,进行了通信。
针对上面的代码,我们可能会有以下疑问:父进程创建出来子进程,父子进程是抢占式执行的,如果是父进程先执行,子进程再执行,那得出上述执行结果顺理成章,父进程往匿名管道写入数据,子进程再从匿名管道读出数据,没有任何问题。但是另一种情况呢?子进程先执行,父进程再执行,子进程去匿名管道读出数据的时候,父进程还没有写入数据,但是依然得出上述执行结果了。那为什么不管怎么运行程序,输出的结果都一致呢?

因为当管道中没有数据的时候,子进程调用的read函数从管道当中读的时候,会阻塞,知道管道当中有内容,read函数读到内容之后,read才会返回。

我们用代码验证一下,在上面代码的基础上,让父进程休眠100秒。

sleep(100);

查看堆栈信息如下:

[jxy@VM-4-2-centos fd]$ ps aux | grep pipe_comm
jxy      11266  0.0  0.0   4212   360 pts/0    S+   11:57   0:00 ./pipe_comm
jxy      11267  0.0  0.0   4212    96 pts/0    S+   11:57   0:00 ./pipe_comm
jxy      11524  0.0  0.0 112816   980 pts/2    S+   11:58   0:00 grep --color=autopipe_comm
[jxy@VM-4-2-centos fd]$ pstack 11266
#0  0x00007f9a950e99e0 in __nanosleep_nocancel () from /lib64/libc.so.6
#1  0x00007f9a950e9894 in sleep () from /lib64/libc.so.6
#2  0x00000000004007e1 in main ()
[jxy@VM-4-2-centos fd]$ pstack 11267
#0  0x00007f9a95113b40 in __read_nocancel () from /lib64/libc.so.6
#1  0x00000000004007bc in main ()

管道的特性

  • 1、半双工·:数据只能从写端流向读端,单向通信
  • 2、匿名管道没有标识符,只能具有亲缘性关系的进程进行进程间通信
  • 3、管道的生命周期是跟随进程的,进程退出了,管道在内核当中就销毁了。
  • 4、管道的大小为64k

我们写段代码验证一下管道的大小:

代码实现思想,只写不读,直到将管道写满。

总共读入了65536个字节,也就是64k,但是程序并没有结束,我们查看一下堆栈信息:

[jxy@VM-4-2-centos fd]$ ps aux | grep pipe_size
jxy      31612  0.6  0.0   4216   356 pts/0    S+   14:19   0:00 ./pipe_size
jxy      31654  0.0  0.0 112816   984 pts/2    R+   14:19   0:00 grep --color=autopipe_size
[jxy@VM-4-2-centos fd]$ pstack 31612
#0  0x00007f495283eba0 in __write_nocancel () from /lib64/libc.so.6
#1  0x0000000000400656 in main ()

为什么程序没有结束呢?因为write往管道当中写的时候阻塞了。

当管道写满之后,再调用write之后,write就会阻塞。

  • 5、管道提供字节流服务

先后两次写入管道的数据之间没有间隔

我们用代码验证一下:

#include <stdio.h>2 #include <unistd.h>3 #include <string.h>4 int main(){5   int fd[2];6   int ret = pipe(fd);//创建管道7   if(ret<0){8     perror("pipe");9     return 0;10   }11 12   write(fd[1],"hello",5);13   write(fd[1],"world",5);//调用两次write14 15   char buf[1024];16   read(fd[0],buf,sizeof(buf)-1);//读17   printf("read buf : %s\n",buf);                                               18   return 0;19 }

执行结果:

[jxy@VM-4-2-centos pipe_stream]$ ./pipe_stream
read buf : helloworld

数据是被读走,而不是被拷贝走,并且读端在进行读的时候,可以按照任意大小去读。

我们再来验证一下:

代码1:

#include <stdio.h>2 #include <unistd.h>3 #include <string.h>4 int main(){5   int fd[2];6   int ret = pipe(fd);7   if(ret<0){8     perror("pipe");9     return 0;10   }11   write(fd[1],"abc",3);                                                        12   char buf[1024]={0};13   read(fd[0],buf,sizeof(buf)-1);14   printf("read buf :%s.\n",buf);15 16   memset(buf,'\0',sizeof(buf));17 18   read(fd[0],buf,sizeof(buf)-1);19   printf("read buf :%s.\n",buf);20   return 0;21 }

执行结果:

程序并没有结束,这是为什么呢?因为read阻塞了,我们查看一下进程的堆栈信息:

[jxy@VM-4-2-centos fd]$ ps aux | grep pipe_stream
jxy       6670  0.0  0.0   4216   356 pts/0    S+   15:04   0:00 ./pipe_stream
jxy       6718  0.0  0.0 112816   988 pts/2    S+   15:05   0:00 grep --color=autopipe_stream
[jxy@VM-4-2-centos fd]$ pstack 6670
#0  0x00007fa1d81a7b40 in __read_nocancel () from /lib64/libc.so.6
#1  0x0000000000400763 in main ()

此时管道中没有任何数据可供第二个read函数读,就造成了阻塞。

代码2:

1 #include <stdio.h>2 #include <unistd.h>3 #include <string.h>4 int main(){5   int fd[2];6   int ret = pipe(fd);7   if(ret<0){8     perror("pipe");9     return 0;10   }11   write(fd[1],"abcdef",6);12   char buf[4]={0};                                                             13   read(fd[0],buf,sizeof(buf)-1);14   printf("read buf :%s.\n",buf);15 16   memset(buf,'\0',sizeof(buf));17 18   read(fd[0],buf,sizeof(buf)-1);19   printf("read buf :%s.\n",buf);20   return 0;21 }

执行结果:

[jxy@VM-4-2-centos pipe_stream]$ ./pipe_stream
read buf :abc.
read buf :def.

这两段代码就验证了我们的数据是被读走,而不是被拷贝走。

  • 6、pipe_size:4096字节

当写入或者是读取的字节数量小于pipe_size的时候,管道保证读写的原子性。

那什么是原子性呢?

一个操作要么不间断的全部被执行,要不一个也没有执行,非黑即白,没有中间状态。对于管道来说,就是读或者写操作在同一时刻只有一个进程在操作,保证进程在操作的时候,读写操作不会被其他进程打扰。

  • 7、阻塞属性

读写两端的文件描述符初始的属性为阻塞属性。当write一直写,读却不去读,则写满之后write会阻塞;当read一直读,当管道内部的数据被读完之后,则read会阻塞,也就是说,当管道为空的时候,调用read,则read函数就会阻塞。

【Linux】进程间通信1-匿名管道1相关推荐

  1. Linux进程间通信(匿名管道)

    管道 管道是Linux中很重要的一种通信方式,是通过把一个程序的输出直接连在另一个程序的输入实现通信的,常说的管道是指匿名管道,它与有名管道之间的最大区别就是只能用于父子进程之间. 管道是一种最基本的 ...

  2. Linux进程间通信——使用命名管道

    在前一篇文章-- Linux进程间通信--使用匿名管道中,我们看到了如何使用匿名管道来在进程之间传递数据,同时也看到了这个方式的一个缺陷,就是这些进程都由一个共同的祖先进程启动,这给我们在不相关的的进 ...

  3. Linux进程通信——匿名管道、命名管道、管道的特性和共享内存

    Linux进程通信--匿名管道.命名管道.管道的特性和共享内存 一.管道 1.1 什么是管道? 1.2 匿名管道 <1> 匿名管道参数说明 <2> fork共享管道原理 < ...

  4. c语言系统编程六:Linux进程间通信之无名管道

    Linux进程间通信之无名管道 一 文件描述符复制 1.1 dup函数(复制文件描述符) 1.2 dup2函数(复制文件描述符) 二 无名管道的概述 三 无名管道的特点 四 无名管道的创建和使用 4. ...

  5. 【编撰】linux IPC 002 - 匿名管道PIPE和有名管道FIFO的概念和实例,以及应用比较

    前言:上一节提到IPC的概述,本一节,原文作者比较详细的讨论了,管道的概念和实例和使用场景: 原文作者:郑彦兴 (mlinux@163.com)国防科大计算机学院 http://www.ibm.com ...

  6. Linux IPC:匿名管道 与 命名管道

    目录 一.管道的理解 二.匿名管道 三.命名管道 四.管道的通信流程 五.管道的特性   进程间通信方式有多种,本文介绍的是管道,管道分为匿名管道和命名管道. 一.管道的理解   生活中的管道用来传输 ...

  7. Linux_进程间通信(进程间通信,匿名管道,命名管道)

    文章目录 1.进程间通信 1.1 进程间通讯概述 1.2 进程间通信目的 1.3 进程间通信分类 2. 管道 2.1 概述 2.2 匿名管道 2.2.1 概述 2.2.2 创建匿名管道 2.2.3 基 ...

  8. 进程间通信(匿名管道、命名管道、共享内存)

    进程间通信 进程间通信的作用 管道 匿名管道 命令感受匿名管道 从内核角度去解释匿名管道 代码创建匿名管道 从PCB角度去分析匿名管道 匿名管道的非阻塞读写特性 创建管道,获取管道读写两端文件描述符的 ...

  9. 进程间通信之匿名管道和命名管道

    目录 管道是什么 匿名管道 命名管道 命名管道创建方式 管道的特点: 管道是什么 概念:管道是计算机通信领域设计者,设计出的一种单向通信的方式,linux原生提供管道通信 管道都是单向传输内容的 管道 ...

  10. 进程间通信(4) 匿名管道

    匿名管道没有名称,所以只能在父进程中调用CreateProcess函数创建子进程时,将管道的读.写句柄传递给子进程. 匿名管道只能实现本地机器上两个进程间的通信,而不能实现跨网络的通信. 1. 过程: ...

最新文章

  1. 秋招要跪?不怕!领走这份机器学习求职攻略
  2. native关键字(本地方法)、 java调用so动态链接库
  3. asp.net 命令行工具初步了解
  4. MATLAB实战系列(三十四)-MATLAB基于PCA-LDA模糊神经网络的人脸识别
  5. Java 线程池详解
  6. WebService 的创建,部署和使用
  7. 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(四)
  8. jquery easyui combobox设置默认选中第一项
  9. 动态规划之一最长上升子序列LIS
  10. 三、项目经理的角色【PMP 】
  11. css3直线运动_CSS3中如何使元素曲线运动
  12. hive sql 怎么实现循环_shell中循环调用hive sql 脚本的方法
  13. java框架常见面试题_java框架面试题总结
  14. java map 遍历取值_遍历 MAP 集合取值方式
  15. 酒店IPTV数字电视系统解决方案
  16. 解决win8 64位版本下无法使用debug
  17. jsp页面打开为空白页
  18. 基于Qt ffmpeg opengl开发跨平台安卓实时投屏软件
  19. 达芬奇密码 第五十九章
  20. 2019网易《Face-to-Parameter Translation for Game Character Auto-Creation》论文解析

热门文章

  1. 提高网页可访问性的十条建议
  2. NSArray技巧两则
  3. gentry同态加密算法_同态加密算法-总结
  4. ElasticSearch 安装教程
  5. 关于批量爬取wallhaven的解析+源码
  6. 当3D打印模型出现层错位时该怎么做?
  7. 任正非:到底什么样的人才能当干部
  8. 【bzoj 4202】石子游戏(博弈论+LCT)
  9. 理解 RxJava 线程模型
  10. 《网络营销实战密码》读后感