进程间通信
管道(数据传输)、共享内存(数据共享)、消息队列(数据传输)、信号量(进程控制)
为什么需要进程间通信?
- 每一个进程都是拥有自己的独立的虚拟地址空间和页表结构,促使了进程独立,导致了进程和进程之间相互协作的问题,为了解决这种问题,才产生了进程间通信。
进程间通信种类的细分:
- 数据传输
- 数据共享
- 进程控制
管道(pipe)
匿名管道
- 我们把从一个进程连接到另一个进程的数据流称为一个管道;
- 需要通信的两个进程在管道的两端;
- 匿名管道就是内核当中的一块缓存;
特性
-
只能用于有亲缘关系的进程之间进行通信
-
是半双工的,数据只能在一个方向流动(从写端到读端)
-
提供字节流式服务 ——如果读端没有及时进行读的话,但是写端往进去写了,后面写的数据会追加在之前写的数据后面。数据其实都是二进制存储的,没有明确的数据边界,如果两次的数据表达的内容是不同的内容,有可能导致读端获取到数据之后,无法知道写端表达的是什么意思。
-
读端读数据的时候,是从管道当中将数据拿走了,并不是当前被读走的数据还存留在管道中。
-
生命周期随进程——进程退出,管道释放
-
内核会对管道操作进行同步和互斥
- 临界资源:同一时间,当前的资源只能被一个进程所访问,由于不同的进程对临界资源访问的时候,不会
- 同步:同一时间,保证只能有一个进程访问资源
- 互斥:保证对临界资源访问的合理性
-
PIPE_SIZE:65536字节,64K(匿名管道的大小) PIPE_BUF:4K大小 ——保证写入数据或读取数据的原子性
- 原子性:当前的操作不能被打断,运行的结果只能有两个,要么是操作完成(1),要么是操作没有完成(0)
创建
pipe()会建立管道,并将文件描述符由参数filedes数组返回
#include <unistd.h>
int pipe(int filedes[2])
- filedes[2]是一个出参,也就是我们在使用该函数的时候,需要传入一个int类型大小为2的数组,在函数内部会将数组的值进行补充,调用完成后之后,就能拿到管道对应的读写端;数组中的每一个元素都是一个文件描述符
- filedes[0]为管道里的读取端,filedes[1]为管道里的写入端
- 返回值:若成功返回0,否则返回-1,错误原因存在errno中
父子进程通信
- 父进程调用pipe函数创建管道,得到两个文件描述符fd[0]、fd[1]指向管道的读端和写端。
- 父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。
- 父进程关闭管道读端,子进程关闭管道写端。父进程可以向管道中写入数据,子进程将管道中的数据读出。由于管道是利用环形队列实现的,数据从写端流入管道,从读端流出,这样就实现了进程间通信。
读写规则
- 读管道
- 管道中有数据,read返回实际读到的字节数
- 管道中无数据:
- 管