进程间通信是什么?
两个或多个进程实现数据层面的交互
因为进程独立性的存在, 导致进程通信的成本比较高
为什么需要进程间通信
1 数据传输:一个进程需要将它的数据发送给另一个进程。
2 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
3 资源共享:多个进程之间共享同样的资源。为了做到这一点,需要内核提供互斥和同步机制。
4 进程控制:有些进程希望完全控制另一个进程的执行(如 Debug 进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
怎么实现进程间通信?
a.进程间通信的本质:必须让不同的进程看到同一份资源
b.资源
其实就是特定形式的内存空间
c.这个“资源”谁提供?
一般是操作系统提供。
为什么不是两个进程中的某个自己提供?
因为进程间具有独立性, 如果这个公共资源是由其中一个提供的, 那这个资源依然是这个进程独有的, 别人还是看不到, 所以这个空间还是得第三方来提供
d. 我们进程想访问这个空间, 本质就是访问操作系统!
进程代表的就是用户, 操作系统一般不允许用户访问自己内部的资源。
所以这个资源
从创建, 到使用, 到释放, 这整个过程 – 都需要使用系统调用接口
操作系统中会有很多个进程之间需要通信, 所以会有很多这样的资源被创建, 操作系统需要管理这些资源
, 所以他会对这些资源先描述再组织
e 通信方式
进程间通信是有标准的
ststem V(本机内部) && posix(网络)
POSIX
还有一种基于文件级的通信方式 – 管道
, 他不属于上述两个标准的任意一个
管道
管道其实就是一个内存级文件
管道的原理
父进程先创建一个内存级文件
, 这个文件内容不会刷新进磁盘, 他的file_operatiors 指向的是对该文件的缓冲区做读写的方法。 然后创建子进程后, 两个进程都指向了这个内存级文件, 当父进程对该文件的缓冲区做写入后, 子进程就可以从缓存区里读信息。
父进程在创建管道的时候, 会将管道既以写方式打开, 又以读方式打开。
然后创建子进程, 最后根据具体谁读谁写关闭不需要的文件。
上述只是一个简洁版。
管道真实是怎么做的呢?
我们父进程先以读方式创建一个文件
然后再以写方式创建一个文件, 这两个文件虽然有不同的file_struct 但是他们指向的是同一个缓冲区。
然后父进程创建出子进程
然后父子进程关闭不需要的文件
可以看出, 我们的管道其实其实是只支持单向通信
的
刚刚讲的都是父子进程之间, 而如果这两个进程之间没有任何的关系, 那么还能用这种方式进行通信吗?
不可以!
只有父子或者兄弟 … 这种具有血缘关系的进程才能通过这种方式通信
由于这个用于通信的管道没有inode 没有路径, 没有名字, 所以我们把这种管道称为匿名管道
讲了这么多其实我们都还没有开始通信
, 我们所做的其实是在建立通信信道
为什么需要进行这么多的操作? 进程间通信是需要成本的
接口 和 代码实现
我们来解释一下这个pipefd【2】的意思,这个参数其实是输出型参数, 他会将你用两种方式打开的内存级文件的fd返回回来, 其中piped[0] 是读 piped[1]是写下标
我们现在试用一下这个接口
这样我们就建立好了单向信道
我们来验证一下子文件的写入功能
最终的读写代码就是这样了, 我们来测试一下
父进程成功的获得了子进程传来的消息;
管道的特性
1 只能让具有血缘关系的的进程进行进程间通信
2 管道只能单向通信
进程间通信, 本质上同一个资源被多执行流共享, 此时就难免会出现访问冲突
。
3 父子进程是会协同的, 同步与互斥 – 保护管道文件的数据安全 (后面才会具体讲)
4 管道是面向字节流的(写端不管写了几次,就一直写, 读段也不管写了几次, 有多少就读多少)
5 管道是基于文件的, 而文件的生命周期是随进程的
管道的4种情况
1 读写端正常, 管道如果为空, 读段就要阻塞
2 读写端正常, 管道如果被写满, 写段就要被阻塞
我们可以使用 ulimit -a
可以看到大小为4kb
我们来验证一下
我们一次写一字节, 看能写多少
写了65536个字节
可以看到是64KB
这是为什么呢?
我们通过查看官方文档发现
3 读端正常, 写端关闭, 那么读段就会读到0 表示读到了文件的结尾, 且不会被阻塞。
4写端正常写入, 读端关闭, 操作系统就会通过发送信号
杀掉正在写入的进程
管道的应用场景
我们执行这段指令
可以看到这三个指令同时变成了进程, 然后他们的ppid都是一样的, 说明他们是同一个进程的子进程。
而这个进程的父进程其实就是bash
而指令间的|
其实就是匿名管道
上述讲的都是具有血缘关系的进程进行通信, 如果是毫不相关的进程之间要进行通信呢?
命名管道
看似这个管道文件是在磁盘上的, 其实他的数据是不会刷新到磁盘的。
当我们将输出重定向到管道文件的时候, 此时进程就阻塞了;
我们切换另一个终端, 发现管道的大小还是0, 说明时间上内容并没有被写进去。
当我们读取管道中的文件后, 之前阻塞的进程就恢复了, 管道里的内容也被正常打印了出来。
其中echo 是一个进程, cat是另外一个进程, 他们两之间没有任何的关系, 可以使用这种命名管道的方式进行通信
理解
- 如果两个进程打开同一个文件,虽然会有两个不同的struct_file但是文件其实只加载了一份, 他们两个能看到的内容是一样的。
所以其实对应的图还是这一个
- 怎么保证两个进程打开的是同一个文件, 只要保证路径加文件名一样即可
所以命名管道其实就是通过使用路径+文件名
的方案, 让不同进程看到同一份资源, 进而实现进程间通信的。
编写代码
在进程中创建管道, 我们需要用到这个系统调用
其中第一个参数是路径加名字, 第二个参数是权限
在进程里删除文件我也需要一个系统调用
其中的参数就是路径加文件名
我们先写一段代码
我们在sever里加上这个一句话来提醒我们打开文件成功
直接运行server却不会打印消息
而是当client也打开文件的时候, 他才会打印消息, 这说明, 在我们client打开文件之前, server他一直处于这段代码
这说明server等待写入方打开后, 自己才会打开文件,向后执行
也就是open 阻塞了
反之写入方也一样, 也会等读端打开文件, 否则就会阻塞;