进程间通信 (IPC)
7.1 基础概念
简单IPC概念
- IPC至少需要两个进程参与: 一个发送者,一个接收者
- 内核为两个进程映射了一段共享内存内存
- 一种常见的通信数据的抽象——消息
三种对比:共享内存 VS 基于共享内存的消息传递 VS 操作系统辅助的消息传递
- 共享内存
- 直接在两个进程间建立共享区域,进程可以直接使用该共享区域上的数据,不存在“消息”的抽象
- 共享内存理论上讲,可以实现零内存拷贝的传输,性能好
- 基于共享内存的消息传递
- 操作系统在通信过程中不干预数据传输
- 多了“消息”的抽象
- 消息传递的基本接口:
- 发送消息
- 接收消息
- 远程过程调用
- 回复消息
- 操作系统辅助的消息传递
- 内核对用户态提供通信的接口
- 缺点:
数据需要经过两次拷贝:- 从发送者用户态内存拷贝到内核内存
- 从内核内存拷贝到接收者用户态内存
- 优点:
- 抽象更简单
- 安全性保证更强,不会破坏发送者和接收者进程的内存隔离性
- 在多方通信时,多个进程间共享内存区域复杂且不安全,操作系统辅助传递可以避免此问题
同步IPC和异步IPC
- 同步IPC指他的IPC操作(如send)会堵塞进程直到该操作完成
- 同步IPC往往是双向IPC(或RPC),即发送者需要等待返回结果
- 异步IPC通常是非堵塞的,进程只要发起一次操作即可返回,而不需要等待其完成
- 异步IPC通常通过轮训内存状态或注册回调函数(如果内核支持)来获取返回结果
同步 VS 异步
堵塞 VS 非堵塞
通信连接管理
- 直接通信
- 通信的进程一方需要显示地标识另一方
- 间接通信
- 需要经过一个中间的信箱来完成通信
- 每个信箱有自己唯一的标识符,而进程间通过共享一个信箱来交换消息
权限检查
- 进程间的通信依赖一套权限检查的机制来保证连接的安全性
- 微内核中,基于Capability的安全检查机制
- linux这样的宏内核中,将安全机制和文件的权限检查结合在一起
System V 进程间通信权限管理
- System V进程间通信通常指的是宏内核下三种具体的进程间通信机制:
- System V 消息队列
- System V 信号量
- System V 共享内存
- 在linux 中,System V 通信的权限管理基于文件的权限检查机制。
- 在linux内核中负责进程间通信权限管理的IPC_PERM 结构体用来检查“访问模式”。
struct ipc_perm { key_t key; uid_t uid; // owner的uid和gid gid_t gid; uid_t cuid; // creator的uid和gid gid_t cgid; mode_t mode; //访问模式 };
7.2 宏内核进程间通信
具体包括:管道,System V 中的消息队列,信号量,共享内存,Linux信号机制,套接字机制
- 宏内核操作系统中进程间通信更多的是应用之间的交互,设计的重心通常会放在接口的易用性、稳定性等方面。
1. 管道进程间通信
- 管道是单向的IPC,内核中通常有一定的缓冲区来缓冲消息。
- 管道通信的数据是字节流,需要用户自己对数据进行解析。
- 管道用于两个进程之间的消息传递。
- 具体实现:
(1) 管道在UNIX系列的系统中会被当做一个文件,它的创建会返回一组(两个文件描述符)
(2) 不过管道实际上不会使用存储设备,而是使用内存作为数据的一个缓冲区 - 管道的行为是FIFO的。
命名管道和匿名管道
- 匿名管道
- 通过pipe系统调用创建
- 问题:
- 只能通过fork继承的方式建立父子之间的连接,对于其他进程间无法使用
- 命名管道
- 通过mkfifo系统调用来创建
- 创建过程中会指定一个全局的文件名(例如 /tmp/namedpipe),通过这种方式,两个进程通过同一个相同的管道名就可以进行通信
2. System V 消息队列
- 消息队列是唯一一个以消息(内核提供的)为数据抽象的通信方式。
消息队列的结构
- 当创建新的消息队列时,内核从系统内存中分配一个队列数据结构,作为消息队列的内核对象。
- 包括:
- 消息头部指针,指向消息队列,同时包含IPC_PERM(消息权限)
- 消息,链表结构的队列,消息包含两部分:类型和数据
基本操作
- msgget
获取已有消息队列的连接或者创建新的消息队列 - msgsnd
往消息队列上发送消息 - msgrcv
从队列接收消息 - msgctl
控制和管理一个消息队列(如修改消息队列的权限或删除一个消息队列)
linux内核实现
- 一个消息队列被创建后,除非内核重启或主动删除,否则其数据会一直保留
- 消息队列的内存空间是有限制的,建议使用共享内存机制来传递长消息,而非使用消息队列
- 消息在用户态和内核态之间传递时,会有拷贝的开销
- 发送消息时,内核通过copy_from_user从用户态搬运到内核空间
- 接收消息时,内核通过copy_to_user将数据搬回用户态
3. System V 信号量
- 信号量在实际的使用中主要用作进程间的“同步”。
- 信号量本省能传递的数据量很少,一般来说仅有一个共享的整形计数器,该计数器由内核维护。
信号量进程间通信
- 信号量的主要操作是两个原语:P 和 V 。
- P 缩写自荷兰语Probeer(尝试),尝试讲一个计数器减1。
- P 操作失败会将当前进程切换到堵塞状态,直到其他进程执行了 V 操作
- V 缩写自荷兰语Verhoog(增加),将一个计数器加1。
- V 操作可能会唤醒一个因 P 操作而陷入堵塞的进程
4. System V 共享内存
- 共享内存的一个很重要原因是性能。
共享内存进程间通信
- 一个或多个进程在其所在的虚拟地址空间中映射相同的物理内存页,从而进行通信。
- 具体实现
- 首先,内核会为全局所有的共享内存维护一个全局的队列结构
- 队列的每一项(shmid_kernel结构体)是和一个IPC key 绑定的
- 各进程可以通过同样一个key,来找到并使用同一段共享物理内存区域。
5. 信号进程间通信
- 信号的特点是单向的时间通知能力。
- 相比而言,信号量也有通知能力,但是需要进程主动去查询计数器状态或陷入堵塞状态(来等待通知)
- 而信号机制接收事件的进程不需要堵塞等待该进程,内核会帮助其切换到对应的处理函数中响应信号事件
信号的基本概念
- 信号传递的信息很短,只有一个编号(信号编号)
- 一个进程会为一些特定的信号编号注册处理函数,当进程接收到对应的信号时,内核会自动的将该用户的控制流切换到对应的处理函数中。
6. 套接字进程间通信
- 套接字是一种既可用于本地,又可跨网络使用的通信机制。
7.3 微内核进程间通信
- 在微内核架构下,原本的系统调用需要通过进程间通信的方式实现。
- 原来的一次系统调用变成了 两次系统调用 + 中间通信处理逻辑的开销。
- 剩下的太多了,不太理解,二刷之后整理