进程间通信(IPC
,InterProcess Communication
)
匿名管道(PIPE)
- 匿名管道也称为 PIPE,它并没有文件实体,通过复制
fd描述符
来实现 - 只能用于具有亲缘关系(父子 / 兄弟)的进程之间的通信
- 匿名管道的生命周期随进程,随着进程的创建而创建,随着进程的销毁而销毁
- 实现:匿名管道创建时会返回两个描述符(读取端描述符
fd[0]
、写入端描述符fd[1]
),这两个描述符都在同一个进程(父进程)里,父进程通过fork()
创建子进程时,子进程会复制父进程的这两个描述符【通信过程:父进程关闭fd[0]
,子进程关闭fd[1]
,这样父进程往管道中写入数据,子进程读取数据】;兄弟进程也是这样,由父进程创建两个子进程,一个子进程关闭读端,一个关闭写端,进行通信
命名管道(FIFO)
- 在不相关的进程间也可以进行通信,因为命名管道提前创建了一个类型为管道的设备文件,通信中进程只要使用同一个设备文件就可以进行通信,可以使用
open()
、read()
、write()
等对文件进行操作,但是没有偏移量的概念,不支持用iseek()
进行文件定位 - 命名管道的数据缓存在内核中,进程写入数据到内核中,然后另一个进程从内核中读取数据,并且通信数据遵循 先来先服务 原则
- 管道传输的是无格式的字节流数据
- 无论是命名管道还是匿名管道,通信效率都非常慢,不适合频繁交换数据
消息队列
通信模式:A 向 B 发送消息,A 将消息写入消息队列,写完之后即可返回,B 等到需要的时候再去消息队列中读取
这种通信模式成功解决了通信效率低、不能频繁交换数据的问题
消息队列保存在内核中的消息链表,发送数据时数据会分成一个个独立的数据单元,称为消息体(数据块)。消息体都是大小固定的存储块,由发送方和接收方商议好消息体的数据类型。当消息体被进程读取后,内核就会将相应的消息体删除。消息队列的生命周期随内核,如果没有释放消息队列或者操作系统,消息队列会一直存在
缺点:通信不及时;传递消息的大小有限制(一个是消息体的大小有限制,另一个是消息队列包含的所有消息体的长度也有限制,所以消息队列不适合传输特别大的数据)共享内存
操作系统有虚拟内存机制,每个进程都有自己的一套虚拟内存空间,不同进程的虚拟内存映射到不同的物理内存,这样即使虚拟地址相同,但映射的物理地址不同,进程之间不会相互影响。
共享内存的机制:两个进程分别拿出一块虚拟内存,映射到相同的物理内存,这样 A 向内存里写东西,B 就能立刻从内存中读取数据,这样极大地提高了通信的效率
共享内存与消息队列相比,不需要将数据拷贝来拷贝去,没有内核态和用户态的转换,极大地提高了通信的效率(采用消息队列的方式,需要先转换为内核态,到内核中放数据或读数据,然后转换为用户态,将数据拷贝到进程中使用)信号量
信号量其实是一个整型的计数器,主要用于实现进程间的互斥与同步,而不是用于缓存进程间通信的数据,它让某一资源在任意时刻只能有一个进程来操作,这样防止两个进程同时操作同一资源而导致操作结果被覆盖
信号量表示资源的数量,操作信号量有两种操作:P、V,这两个操作必须成对出现的
- P 操作:将信号量 + 1,该操作用在获取共享资源之前
- V 操作:将信号量 - 1,该操作用在释放共享资源之后
信号
在异常情况下的工作模式中,需要用到信号来通知进程。信号是进程间通信机制中唯一的异步通信机制,可以在任何时候发送信号给某一进程套接字(Socket)
管道、消息队列、共享内存、信号量、信号都是在同一主机上进行的通信
而 Socket 实际上不仅用于不同的主机进程间通信,还可以用于本地主机进程间通信,可根据创建 Socket 的类型不同,分为三种常见的通信方式,一个是基于 TCP 协议的通信方式,一个是基于 UDP 协议的通信方式,一个是本地进程间通信方式。
参考资料