目录
一.理解进程间通信
1.进程间通信的意义
2.进程间如何实现通信呢?
二.匿名管道
1.匿名管道的底层原理
引用计数的应用
2.匿名管道代码实现
a.代码的整体框架
b.写接口
c.读接口
d.子进程资源回收
3.匿名管道的官方接口
4.*匿名管道四种情况和五种特性
a.管道的四种情况
b.管道的5种特性
5.匿名管道的应用(进程池)
三.命名管道
1.命令管道通信原理
2.指令级创建命名管道
3.命名管道代码实现
a.服务端创建命名管道,并向管道内写入数据
①创建命名管道文件
②删除命名管道文件
b.客户端从管道文件中读取数据
c.源码链接
4.命名管道的特点
一.理解进程间通信
1.进程间通信的意义
由于进程独立性,不同进程的资源(进程地址空间、文件描述符表、页表...等)是不共享的,所以,如果我们想实现多进程间协同工作,如:数据传输、资源共享、事件通知、进程控制...等,就需要进程间通信!!
2.进程间如何实现通信呢?
进程间通信的本质,就是让不同的进程能够看到同一份公共资源,这份资源通常是由操作系统提供的,而根据这块“公共资源”的不同,进程间通信的机制也就不同。常见进程间通信的方式有以下几种:①管道通信(基于文件);②共享内存通信(基于物理内存);③信号(基于中断);④消息队列(基于队列)
本文章主要讲的是管道通信,也就是让不同的进程看到同一份文件,一个进程向文件中写入数据,另一部分进程从文件中读取数据,实现最简单的进程间通信。
而管道通信,又分为:匿名管道和命名管道两种。且听博主细细道来~~
二.匿名管道
1.匿名管道的底层原理
我们知道,fork()创建子进程时,子进程会以父进程为模版,重新拷贝一份资源,而这份资源中就包括父进程的文件描述符表,而文件描述符表内存放的是,内存中被打开文件struct file结构体的地址。
换句话说,有了文件描述符表,就能找到文件结构体地址,有了地址,就能访问对应文件!!
这样一来,我们仅需在父进程中分别以读和写的方式打开一个文件,然后调用 fork() 创建子进程,由于父进程的文件描述符相同。所以,二者都可以访问这个文件,一个进程向文件中写入数据,另一进程从文件中读取数据,这就是匿名管道的底层原理!!
一个进程,把一个文件分别以读和写两种方式打开后,由于进程对文件内容数据的读取和写入位置不同,所以系统会生成两个fd文件描述符,一个供进程写入数据,另一个供进程读取数据。
图解如下:
引用计数的应用
上述中,父进程用读方式打开文件的同时,又用写方式打开该文件,并在文件描述符表中生成了两个fd,那么问题来了,当父进程用 close() 关闭其中一个fd时,该文件会不会被关闭呢?如果不会关闭,那该文件什么情况下才会真正被关闭呢?
答:文件的struct file内有一个引用计数,文件每被打开一次,这个引用计数都会++;文件每被关闭一次,引用计数都会--;当该引用计数减到0时,文件就会关闭!!
引用计数的应用场景:
1.硬链接:磁盘中与inode产生映射关系的文件名
2.内核数据结构中,指向struct file结构体的文件描述符的个数
3.shared_ptr智能指针
2.匿名管道代码实现
a.代码的整体框架
b.写接口
c.读接口
d.子进程资源回收
3.匿名管道的官方接口
官方给我们提供的 pipe() 接口,功能与我们上图手写的类似,唯一不同的是, pipe() 创建的是管道文件,read() 等接口与文件进行数据 IO 时,若读取到文件数据的末尾,read 并不会返回0,而是会一直阻塞,除非写端口关闭,read 才会返回0!!
注意:int pipefd[2] 是输出型参数,返回的两个值分别是以读和写的方式打开该管道的进程描述符数组的下标,即 fd1和 fd2。
4.*匿名管道四种情况和五种特性
a.管道的四种情况
1.如果管道内没有数据了,读端必须等待,直到有数据为止.
2.如果管道被写满了,写端必须等待,直到有空间为止.
3.如果写端关闭,读端会一直读,当读端会读到read返回值为0时,表示已经读到文件结尾.
4.如果读端关闭,写端一直写的话,OS会直接kill写端进程,即通过向写端进程发生SIGPIPE(13)信号,终止目标进程.
b.管道的5种特性
1.匿名管道,只允许具有血缘关系的进程之间进行通信,常用于父子进程.
2.匿名管道,默认给读写端要提供同步机制,即两个进程的执行具有一定的顺序性.
3.*面向字节流(多次写入,一次读出;一次写入,多次读出).
4.管道的生命周期是随进程的.
5.管道是单向通信(只能一端写,另一端读)的,半双工通信的一种特殊情况.
5.匿名管道的应用(进程池)
进程池:在系统调用层面创建进程是需要花费一定的时间和空间代价的,所以为节省用户的时间成本,OS会提前创建多个进程去等待用户下达的任务,我们将这些提前创建好的进程叫做进程池。
底层原理:
*进程池代码实现——基于匿名管道
源码链接:https://gitee.com/Coder-Li-YuJie/inter-process-communication
三.命名管道
1.命令管道通信原理
先前实现的进程池只能是具有血缘关系的进程才能进行通信,那么两个毫不相干的进程间如何使用管道进行通信?? --- 命名管道!!
匿名管道:父进程打开文件,而后创建子进程,通过文件描述符表的继承性,这样父子进程就可以基于同一个文件进行通信。
命名管道:直接让两个毫不相干的进程打开同一份文件,而后直接基于文件进行通信!!
原理:因为路径具有唯一性,所以,我们可以使用路径+文件名,来唯一的让不同进程看到同一份资源!两个进程通过打开同一份文件,向该文件缓冲区内写入或读取数据,并且文件缓冲区内的数据不会刷新到磁盘上。
两个进程打开同一份文件,内存里的文件缓冲区和文件的属性数据只会加载一份!!
2.指令级创建命名管道
创建一个管道文件的指令 —— mkfifo
如:mkfifo fifo
echo "hello world" > myfifo(向管道文件输入数据时,阻塞,因为管道的“读”端未打开)
将管道的“读”端打开后
管道文件的大小一直是0 ??
--- 这个管道是一个创建出来的磁盘级符号(内存级文件),在进行进程间通信的时候,就算该文件被打开,它的数据也不会向磁盘进行刷新!
3.命名管道代码实现
a.服务端创建命名管道,并向管道内写入数据
①创建命名管道文件
②删除命名管道文件
b.客户端从管道文件中读取数据
现象:只有当两个进程同时以读的方式和写的方式打开同一管道文件时,两个进程的代码才能够继续向后执行,否则,若写端打开,读端没打开,则写端进程等待;反之,亦是如此!!!!!
c.源码链接
https://gitee.com/Coder-Li-YuJie/naming-pipes-and-shared-memory
4.命名管道的特点
① 双向通信:虽然传统的管道是单向的(一个进程写,一个进程读),但命名管道可以被设计为双向的,允许两个进程既可以写入也可以读取数据。
②持久性:命名管道在文件系统中有一个名称,即使创建它的进程已经终止,该管道仍然存在,直到它被显式删除,如调用上述 DelFifo() 接口。
③多进程通信:传统的管道只能用于具有共同祖先的进程间的通信(例如,父子进程)。而命名管道可以被不相关的进程用来通信。