Lec 12 进程间通信
Lec 12 进程间通信
License
本内容版权归上海交通大学并行与分布式系统研究所所有
使用者可以将全部或部分本内容免费用于非商业用途
使用者在使用全部或部分本内容时请注明来源
资料来自上海交通大学并行与分布式系统研究所+材料名字
对于不遵守此声明或者其他违法使用本内容者,将依法保留追究权
本内容的发布采用 Creative Commons Attribution 4.0 License
完整文本
使用多个进程的应用
- 一些应用程序选择使用不同进程来运行不同模块
- 优势-1:功能模块化,避免重复造轮子(如数据库、界面绘制)
- 优势-2:增强模块间隔离,增强安全保障(敏感数据的隔离)
- 优势-3:提高应用容错能力,限制故障在模块间的传播
- 然而不同进程拥有不同的内存地址空间
- 进程与进程之间无法直接进行通信和交互
- 需要一种进程间通信的方式
- IPC:Inter-Process Communication
常见IPC的类型
| IPC机制 | 数据抽象 | 参与者 | 方向 |
|---|---|---|---|
| 管道 | 文件接口 | 两个进程 | 单向 |
| 共享内存 | 内存接口 | 多进程 | 单向/双向 |
| 消息队列 | 消息接口 | 多进程 | 单向/双向 |
| 信号 | 信号接口 | 多进程 | 单向 |
| 套接字 | 文件接口 | 两个进程 | 单向/双向 |
IPC接口类型
- 已有接口
- 内存接口:共享内存;文件接口:管道(Pipe)、套接字(Socket)
- 新的接口
- 消息接口、信号接口等
- 简单IPC的消息接口
- 发送消息:Send(message)
- 接收消息:Recv(message)
- 远程方法调用:RPC(req_message, resp_message)
- 回复消息:Reply(resp_message)
简单IPC设计与实现
信息的发送

发送者和消费者都依赖于一个通信连接channel,作为媒介进行信息的传输。
信息的接收

信息的远程方法调用(receiver)

简单IPC的两个阶段
- 段階1:准备阶段
- 建立通信连接,即进程间的信道
- 假设内核已经为两个进程映射了一段共享内存
- 建立通信连接,即进程间的信道
- 段階2:通信阶段
- 数据传递
- "消息"抽象:通常包含头部(含魔数)和数据内容(500字节)(一般不包含指针)
- 通信机制
- 两个消息保存在共享内存中:发送者消息、接收者消息
- 发送者和接收者通过轮询消息的状态作为通知机制
- 数据传递

简单IPC数据传递的两种方法
- 基于共享内存的数据传递
- 操作系统在通信过程中不干预数据传输
- 操作系统仅负责准备阶段的映射
- 优势:无需切换到内核态即可完成IPC(多核场景下),完全由用户态控制,定制能力更强,可实现零内存拷贝(无需内核介入)
- 基于操作系统辅助的数据传递
- 操作系统提供接口(系统调用):Send、Recv
- 通过内核态内存来传递数据,无需在用户态建立共享内存
- 优势:抽象更简单,用户态直接调用接口,使用更方便,安全性保证更强,发送者在消息被接收时通常无法修改消息,多方(多进程)通信时更灵活、更安全
简单IPC的通知机制
- 基于轮询(消息头部的状态信息)。
- 缺点:大量CPU计算资源的浪费。
- 基于控制流的转移
- 内核控制进程的运行状态
- 进程只有在条件满足下进行,避免CPU浪费。

简单IPC的方向:单向&双向
- 简单IPC的一次完整通信过程包含两个方向的通信
- 发送者传递一个消息(即请求)给接收者
- 接收者返回一个消息(即结果)给发送者
- 通信的三种可能方向
- 仅支持单向通信
- 仅支持双向通信(可基于单向通信实现)
- 单向和双向通信均可(根据配置来选择)
IPC控制流:同步&异步
- 同步IPC
- IPC操作阻塞进程直到操作完成。
- 线性控制流。
- 调用者继续运行时,返回结果已经OK
- 异步IPC
- 进程发起IPC操作后,立即返回。无需等待完成。
- 通过轮询或回调函数(需要内核的支持)来获取返回结果

IPC超时机制
- 一种新的错误:超时
- 传统的函数调用不存在超时问题
- IPC涉及两个进程,分别有独立的控制流
- 超时可能的原因
- 被调用者是恶意的:故意不返回
- 被调用者不是恶意的:运行时间过长、调度时间过长、请求丢失等
- 超时机制
- 应用可自行设置超时的阈值,但如何选择合适的阈值却很难
- 特殊的超时机制:阻塞、立即返回(要求被调用者处于可立即响应的状态)
IPC通信连接
- 方法1:直接通信
- 通信的一方需要显示地标识另一方,每一方都拥有唯一标识
- 如:Send(P, message), Recv(Q, message)
- 连接的建立是自动完成的(由内核完成)
- 方法2:间接通信
- 通信双方通过"信箱"的抽象来完成通信
- 每个信箱有自己唯一的标识符
- 通信双方并不直接知道在与谁通信
- 进程间连接的建立发生在共享一个信箱时
IPC权限检查
- 宏内核
- 通常基于权限检查的机制实现
- 如:Linux中与文件的权限检查结合在一起(以后介绍)
- 微内核
- 通常基于Capability安全检查机制实现
- 如seL4将通信连接抽象为内核对象,不同进程对于内核对象的访问权限与操作有Capability来刻画
- Capability保存在内核中,与进程绑定
- 进程发起IPC时,内核检查其是否拥有对应的Capability
IPC命名服务
- 命名服务:一个单独的进程
- 类似一个全局的看板,协调服务端与客户端之间的信息
- 服务端可以将自己提供的服务注册到命名服务中
- 客户端可以通过命名服务进程获取当前可用的服务
- 命名服务的功能:分发权限
- 例如:文件系统进程允许命名服务将连接文件系统的权限任意分发,因此所有进程都可以访问全局的文件系统
- 例如:数据库进程只允许拥有特定证书的客户端连接
IPC小结


管道(pipe):文件接口的IPC
- 管道是Unix等系统中常见的进程间通信机制
- 管道(Pipe): 两个进程间的一根通信通道
- 一端向里投递,另一端接收
- 管道是间接消息传递方式,通过共享一个管道来建立连接
- 例子: 我们常见的命令 ls | grep xxx
- 优点: 设计和实现简单
- 针对简单通信场景十分有效
- 问题:
- 缺少消息的类型,接收者需要对消息内容进行解析
- 缓冲区大小预先分配且固定
- 只能支持单向通信
- 只能支持最多两个进程间通信
- 传统的管道缺乏名字,只能在有亲缘关系的进程间使用
- 也称为“匿名管道”
- 通常通过fork,在父子进程间传递fd
- 命名管道:具有文件名
- 在Linux中也称为fifo,可通过mkfifo()来创建
- 可以在没有亲缘关系的进程之间实现IPC
- 允许一个写端,多个读端;或多个写端,一个读端
共享内存(内存接口的IPC)



- 存在问题
- 缺少通知机制
- 若轮询检查,则导致CPU资源浪费
- 若周期性检查,则可能导致较长的等待时延
- 根本原因:共享内存的抽象过于底层;缺少OS更多支持
- TOCTTOU (Time-of-check to Time-of-use)问题
- 当接收者直接用共享内存上的数据时,可能存在被发送者恶意篡改的情况(发生在接收者检查完数据之后,使用数据之前)
- 这可能导致buffer overflow等问题
消息传递(message passing)
消息队列
- 一种消息传递机制
- 设计选择:
- 间接通信方式,信箱为内核中维护的消息队列结构体
- 有(有限的)缓存
- 没有超时机制
- 支持多个(大于2)的参与者进行通信
- 通常是非阻塞的(不考虑如内核缓存区满等异常情况)
- 消息队列: 以链表的方式组织消息
- 任何有权限的进程都可以访问队列,写入或者读取
- 支持异步通信 (非阻塞)
- 消息的格式: 类型 + 数据
- 类型:由一个整型表示,具体的意义由用户决定
- 消息队列是间接消息传递方式
- 通过共享一个队列来建立连接

消息队列:具有类型的消息传递
- 消息队列的组织
- 基本遵循FIFO (First-In-First-Out)先进先出原则
- 消息队列的写入:增加在队列尾部
- 消息队列的读取:默认从队首获取消息
- 允许按照类型查询: Recv(A, type, message)
- 类型为0时返回第一个消息 (FIFO)
- 类型有值时按照类型查询消息
- 如type为正数,则返回第一个类型为type的消息
与管道pipe的对比
- 缓存区设计:
- 消息队列: 链表的组织方式,动态分配资源,可以设置很大的上限
- 管道: 固定的缓冲区间,分配过大资源容易造成浪费
- 消息格式:
- 消息队列: 带类型的数据
- 管道: 数据 (字节流)
- 连接上的通信进程:
- 消息队列: 可以有多个发送者和接收者
- 管道: 两个端口,最多对应两个进程
- 消息的管理:
- 消息队列: FIFO + 基于类型的查询
- 管道: FIFO
- 消息队列更加灵活易用,但是实现也更加复杂
轻量级远程方法调用LRPC
IPC会带来较大的性能损失
传统的进程间通信机制通常会结合以下机制:
- 通知:告诉目标进程事件的发生
- 调度:修改进程的运行状态以及系统的调度队列
- 传输:传输一个消息的数据过去
缺少一个轻量的远程调用机制
- 客户端进程切换到服务端进程,执行特定的函数 (Handler)
- 参数的传递和结果的返回
Lightweight Remote Procedure Call (LRPC)
解决两个主要问题
- 控制流转换: Client进程快速通知Server进程
- 数据传输: 将栈和寄存器参数传递给Server进程

- 控制流转换:调度不确定导致时延
- 控制流转换需要下陷到内核
- 内核系统为了保证公平等,会在内核中根据情况进行调度
- Client和Server之间可能会执行多个不相关进程

迁移线程: 将Client运行在Server的上下文
为什么需要做控制流转换?
- 使用Server的代码和数据
- 使用Server的权限 (如访问某些系统资源)
只切换地址空间、权限表等状态,不做调度和线程切换
数据传输: 数据拷贝的性能损失
大部分Unix类系统,经过内核的传输有(至少)两次拷贝
- Client \(\to\) 内核 \(\to\) Server
数据拷贝:
- 慢: 拷贝本身的性能就不快 (内存指令)
- 不可扩展: 数据量增大10x,时延增大10x

共享参数栈和寄存器
参数栈 (Argument stack,简称A-stack)
- 系统内核为每一对LRPC连接预先分配好一个A-stack
- A-stack被同时映射在Client进程和Server进程地址空间
- Client进程只需要将参数准备到A-stack即可
- 不需要内核额外拷贝
执行栈(Execution stack,简称E-stack)
共享寄存器
- 普通的上下文切换: 保存当前寄存器状态 → 恢复切换到的进程寄存器状态
- LRPC迁移进程: 直接使用当前的通用寄存器
- 类似函数调用中用寄存器传递参数
轻量远程调用:通信连接建立
Server进程通过内核注册一个服务描述符
- 对应Server进程内部的一个处理函数(Handler)
内核为服务描述符预先分配好参数栈
内核为服务描述符分配好调用记录 (Linkage record)
- 用于从Server进程处返回(类似栈)
内核将参数栈交给Client进程,作为一个绑定成功的标志
- 在通信过程中,通过检查A-stack来判断Client是否正确发起通信

一次调用过程:轻量远程调用
- 内核验证绑定对象的正确性,并找到正确的服务描述符
- 内核验证参数栈和连接记录
- 检查是否有并发调用 (可能导致A-stack等异常)
- 将Client的返回地址和栈指针放到连接记录中
- 将连接记录放到线程控制结构体中的栈上 (支持嵌套LRPC调用)
- 找到Server进程的E-stack (执行代码所使用的栈)
- 将当前线程的栈指针设置为Server进程的运行栈地址
- 将地址空间切换到Server进程中
- 执行Server地址空间中的处理函数
- 为什么需要将栈分成参数栈和运行栈?
- 参数栈是为了共享传递参数,而执行栈是为了执行代码已经处理局部变量等使用的
- LRPC中控制流转换的主要开销是什么?
- 地址空间的切换(来自硬件限制)是最主要的性能开销
- 在不考虑多线程的情况下,共享参数栈是否安全?
- 安全的。因为是同步IPC,所以在被调用者上下文执行的时候,其实没有其他人可以去读写A-stack。
Example: Chcore
- 通信进程直接切换
- 启发自LRPC和L4直接切换技术
- 同步的通信
- 通过共享内存传输大数据
- 基于Capability的权限控制
- 类似Unix文件描述符的权限机制,Capability表示一个线程/进程对于系统资源的具体权限
建立通信连接流程
- 服务端进程在内核中注册服务
- 客户端进程向内核申请连接目标服务端进程的服务
- 可选: 设置共享内存
- 内核将客户端请求请求转发给服务端
- 服务端告诉内核同意连接 (或拒绝)
- 可选: 设置共享内存
- 内核建立连接,并把连接的Capability返回给客户端
- 或返回拒绝
发起通信
- 客户端进程通过连接的Capability发起进程间通信请求
- 内核检查权限,若通过则继续步骤3,否则返回错误
- 内核直接切换到服务端进程执行 (不经过调度器)
- 将通信请求的参数设置给服务端进程的寄存器中
- 服务端处理完毕后,通过与步骤3相反的过程将返回值传回客户端
Lec 12 进程间通信的更多相关文章
- 算法导论——lec 12 平摊分析与优先队列
在平摊分析中,运行一系列数据结构操作所须要的时间是通过对运行的全部操作求平均得出.反映在不论什么情况下(即最坏情况下),每一个操作具有平均性能.掌握了平摊分析主要有三种方法,聚集分析.记账方法.势能方 ...
- python 各模块
01 关于本书 02 代码约定 03 关于例子 04 如何联系我们 1 核心模块 11 介绍 111 内建函数和异常 112 操作系统接口模块 113 类型支持模块 114 正则表达式 115 语言支 ...
- Python Standard Library
Python Standard Library "We'd like to pretend that 'Fredrik' is a role, but even hundreds of vo ...
- 在mybatis中写sql语句的一些体会
本文会使用一个案例,就mybatis的一些基础语法进行讲解.案例中使用到的数据库表和对象如下: article表:这个表存放的是文章的基础信息 -- ------------------------- ...
- Linux学习笔记(12)-进程间通信|匿名管道
Linux的进程间通信有几种方式,包括,管道,信号,信号灯,共享内存,消息队列和套接字等-- 现在一个个的开始学习! ----------------------------------------- ...
- 8.12 day31 进程间通信 Queue队列使用 生产者消费者模型 线程理论 创建及对象属性方法 线程互斥锁 守护线程
进程补充 进程通信 要想实现进程间通信,可以用管道或者队列 队列比管道更好用(队列自带管道和锁) 管道和队列的共同特点:数据只有一份,取完就没了 无法重复获取用一份数据 队列特点:先进先出 堆栈特点: ...
- C++进程间通信
# C++进程间通信 # 进程间通讯的四种方式:剪贴板.匿名管道.命名管道和邮槽 ## 剪切板 ## //设置剪切板内容 CString str; this->GetDlgItemText(ID ...
- Unix/Linux进程间通信(二):匿名管道、有名管道 pipe()、mkfifo()
1. 管道概述及相关API应用 1.1 管道相关的关键概念 管道是Linux支持的最初Unix IPC形式之一,具有以下特点: 管道是半双工的,数据只能向一个方向流动:需要双方通信时,需要建立起两个管 ...
- [转]Windows进程间通信的各种方法
http://www.cnblogs.com/songQQ/archive/2009/06/03/1495764.html 道相似,不过它传输数据是通过不可靠的数据报(如TCP/IP协议中的UDP包) ...
- 12个Linux进程管理命令介绍(转)
12个Linux进程管理命令介绍 [日期:2015-06-02] 来源:Linux中国 作者:Linux [字体:大 中 小] 执行中的程序在称作进程.当程序以可执行文件存放在存储中,并且运行的 ...
随机推荐
- ai 赋能
独立平台 chatGpt(推荐) 点击这里使用:https://chatgpt.com 排名第一,实至名归,是 OpenAI 公司开发的一种基于 GPT 模型的对话生成系统,主要用于人机交互,如聊天机 ...
- JavaScript 异步编程指南:async/await 与 Promise 该怎么选?
在 JavaScript 开发中,异步操作就像家常便饭 -- 从调用后端 API 到读取本地文件,几乎无处不在.但很多开发者都会困惑:到底该用 Promise 的链式调用,还是 async/await ...
- vim 的复制与黏贴
http://www.cnblogs.com/MMLoveMeMM/articles/3707287.html 转载自 用vim写代码时,经常遇到这样的场景,复制多行,然后粘贴. 这样做:1. 将光标 ...
- ETL过程中数据精度不准确问题
最近一位同学在使用Restcloud ETL产品做数据集成,出现数据传输到目标库表后,数据精度不准确问题. 场景为:从oracle源表数据 格式为:number(21,6)将数据同步到mysql目标表 ...
- SciTech-Mathmatics - Advanced Linear Algebra(高等线性代数): Vector、Vectors、Vector Space 和 Matrix 的奇妙联系
SciTech-Mathmatics - Advanced Linear Algebra(高等线性代数): Vector.Vectors.Vector Space和 Matrix 的奇妙联系 \(\l ...
- [NISACTF 2022]babyserialize
非常典型的一道POP链构造 题目源码 <?php include "waf.php"; class NISA{ public $fun="show_me_flag& ...
- js实现根据汉字的拼音按照a-z的方式进行排序
需求的产生 今天在需求评审的过程中,遇见一个排序问题 地区的拼音按照a-z的顺序进行排序. 研究了一下,主要有下面三种做法. 1,使用 String.prototype.localeCompare() ...
- 修复fstab文件引起的系统故障
修复fstab文件引起的系统故障 进入系统救援模式,修复故障 通过光盘启动系统,进入救援模式 进入BIOS设置,设置光盘启动 启动后选择最后一个选项 选择救援CentOS系统 选择1 继续 此时根 ...
- Bugku刷题_秋名山车神
仅供个人学习记录使用,如有错误请各位佬多多指教 题目让我们计算这一串数字运算之后的值,还有一个提示是让我们post一个value,值就是计算之后的值. 两秒计算这么复杂的数字那就只能上脚本了.(作为一 ...
- 从“AI辅助”到“AI主导”:高质量开发文档如何成为引爆AI编程潜力的关键
AI编程的"蜜月期"与"阵痛期" 2025年,AI编程早已不是一个新概念.从GitHub Copilot的代码补全,到Cursor.Claude Code这类能 ...