您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Model Center 汽车系统工程   模型库  
会员   
   
基于UML和EA进行分析设计
2月3-4日 北京+线上
需求分析与管理
2月9-10日 北京+线上
AI大模型编写高质量代码
3月12-13日 北京+线上
     
   
 订阅
IPC(进程间通信:Inter-Process Communication)
 
作者:oi嘉菲猫
  146   次浏览      6 次
 2026-1-26
 
编辑推荐:
本文介绍了进程间通信IPC相关的知识。 希望能为大家提供一些参考或帮助。
文章来自于51CTO,由火龙果Linda编辑推荐。

1.IPC的本质理解

进程间通信不是目的而是手段,目的是为了实现进程协同。

1.1IPC的目的

IPC的底层背景:

进程之间独占各自的 虚拟地址空间+用户级页表 保证了进程的独立性(进程的内核数据结构+进程的代码和数据),但也导致了进程之间想要进行通信的难度比较高。

IPC的本质理解:

1)IPC的前提:首先要让不同的进程看到同一份资源(特定的结构组织,通常就指的是内存)。

2)不同进程看到的资源属于谁呢?不能隶属于任何一个进程,而应该更强调共享的概念。

IPC的目的:

  • 传输数据:一个进程需要将自己的数据发送给另一个进程。

  • 资源共享:多个进程共享同样的资源。

  • 通知事件的发生:一个进程需要向另一个/一组进程发送消息,通知某种事件的发生(如子进程终止需要通知父进程)。

  • 进程控制:有些进程希望能够完全控制另一个进程的执行(如Debug),此时控制进程希望能够拦截一个进程的所有陷入内核和异常,并能够即使知道它的状态改变。

1.2IPC的分类

1)管道 —— Linux原生提供的,源于Linux的文件管理的设计。

  • 匿名管道

  • 命名管道

2)System V IPC —— System V标准实现的IPC

  • System V 消息队列(不常用)

  • System V 共享内存

  • System V 信号量(简单原理)

3)POSIX IPC(多线程 + 网络通信) —— POSIX标准实现的IPC

  • 消息队列

  • 共享内存

  • 信号量

补充:

1)标准:在使用者看来就是提供的接口具有一定的规律;其本质是规定了应该提供什么样的接口实现什么样的功能,内部会又复杂的逻辑,而不会提供具体的实现。

2)System V 和 POSIX(Portable Operator System Interface:可移植操作系统接口,X表示设计为不限于只在Unix上使用)都是一整套的操作系统的交互标准,IPC只是其中的进程间通信的标准规范。

2.管道(文件)

2.1管道本质

2.2.1管道的理解

现实中的管道总结:有入口有出口 —— 只能单向通信 —— 管道中传输的都是资源(在计算机通信中为数据)。

所以管道的定义:一个进程连接到另一个进程的一个单向数据流。是Unix中最古老的进程间通信。

特别注意:并不是说管道只能是从一个进程到另一个进程,而是说单向性(方向)和出入口(访问控制)。

由于IPC的前提是需要让不同进程看到同一份内存,而只有OS才能直接接触到内存资源,所以必须由OS提供管道(其实是源于Linux的文件管理方式)。

2.2.2管道的原理

1)基础原理

只要不同的进程分别以读和写的方式打开同一份文件,然后一个进程写入、一个进程读取就可以实现进程间通信了。

2)深层原理

补充:用于描述文件的内核结构体struct file除了有指向描述文件属性的inode之外,也有内核缓冲区来避免频繁的与外设IO。

管道的原理:两个进程分别以读写方式打开同一个文件之后,就可以进行通信,但是对于这个文件而言,它只是用于实现IPC,完全没必要将数据写到磁盘上持久化(还会减慢IO速度),所以这个文件只存在于内存(内核缓冲区)中,是内存级文件。

总结:看待文件就如同看对文件一样!管道的使用和文件一致,迎合了“Linux一切皆文件思想”。所以管道的本质就是文件。(也成为管道文件)

补充:

1)匿名管道和命名管道的原理都是一样的,区别在于让不同进程看到同一个文件的方法不同。

2)OS不需要通过inode才能看到文件的数据页(内核缓冲区),而是直接通过文件对象file就能查到。

2.2匿名管道

匿名管道模拟单机负载均衡

2.2.1访问同一个文件实现方法

子进程会将父进程的文件描述符表拷贝一份,这属于进程管理范畴。而文件对象不会再拷贝一份,这属于文件管理部分。

创建子进程后,父子进程的文件描述符表相同,指向相同的文件对象(文件),再关闭掉自己不使用的文件描述符就可以进行通信了。这就是匿名管道。

即,匿名管道通过创建子进程的方式让不同进程能够访问同一个文件资源。—— 先创建子进程,再关闭不不使用的文件描述符。

2.2.2创建匿名管道的系统调用

pipefd[2]:创建的管道文件的文件描述符数组,其中[0]为读取、[1]写入;

返回值:创建成功返回0,失败返回-1并设置errno;

补充:pipe2的使用涉及到多线程的线程安全问题以及数据包。

2.2.3管道的读写规则

1)写快、读慢:写满了就不能再写,阻塞等待。

2)写慢、读快:管道中没有数据的时候,阻塞等待写入。

3)写端关闭:读端继续读完,没有数据了读到0,标识读到了文件结尾。

4)读端关闭:写端继续写,OS终止写进程。(本质写端进程是收到SIGPIPE信号)

2.2.4管道的特点

1)管道是用来进行具有血缘关系的进程进程间通信的。—— 常用于父子进程。

2)管道具有让进程协同的功能,提供了访问控制(一个会等另一个)。

3)管道是面向字节流的通信服务(字节一个接一个地从写端流向读端)。—— 协议。

4)管道是基于文件的,文件的生命周期是随进程的,所以管道的生命周期随进程。

5)管道是单向半双工通信。

其中,只有1)是匿名管道独有的特点,其他的是管道的特点。

补充:

1)文件控制的系统调用fcntl,可以设置文件的相关信息。

2)PIPE_BUFF(一个内存页:4KB):使用管道时写入小于PIPE_BUFF才能保证数据的原子性。

3)原子性(auto_mic):一件事要么做了,要么没做,没有中间状态。(语言层面的一条语句对应汇编的多条语句/对应可执行程序的多条指令,所以CPU执行指令时有可能导由于进程切换等原因导致操作处于中间态)。

2.3命名管道

用来解决匿名管道一定要有血缘关系的限制。

 命名管道模拟客户端与服务器的通信

2.3.1访问同一个文件实现方法

命名管道通过创建一个在文件系统中只有文件索引而没有文件内容(无论怎么写入大小为0)的特殊文件(管道文件),这个文件在加载到内存的时候就是用内核缓冲区来提供通信。

管道文件:在文件系统中显示,并且可以被打开,到那时加载到内存中不会将内存数据刷新到磁盘。—— 有名字、有路径,保证唯一性。—— 不进程就可以看到同一份管道文件。

即,任意两个进程可以分别以读和写的方式打开这个特殊文件进行通信。

2.3.2创建命名管道的系统调用

pathname:创建管道文件的路径和文件名;

mode:管道文件的读/写/执行权限;

返回值:成功返回0,失败返回-1;

 

注意:如果需要创建的管道文件已经存在,则mkfifo会失败。

2.3.3匿名管道与命名管道的区别

  • 匿名管道通过pipe创建并打开。

  • 命名管道通过mkfifo创建,使用open打开。

  • FIFO(命名管道)与PIPE(匿名管道)之间唯一的区别在于创建和打开方式不同,其他的具有相同的语义。

补充:管道不一定是一对一的。可以是一对多的,然后竞争读取。甚至多对一的,竞争写入,不保证读取端的顺序。

3.System V共享内存

共享内存+管道实现共享内存的访问控制

3.1共享内存原理

用户向OS另外申请一块额外的内存空间,不同的进程再分别把申请拿到的内存空间映射到自己的进程地址空间中就可以同时进行读取和写入了。

共享内存创建映射好之后,可以不经过系统调用直接进行内存级的访问,而管道需要通过reaed、write等系统调用接口才能访问。

这是因为共享内存虽然数据存储在物理内存中,但是已经把物理内存存储数据的部分映射到进程地址空间中,相当于就存储在进程当中,实际上就是”一个很大的数组“。而管道是文件,写入数据存储在内核缓冲区。

补充:共享内存时OS专门设立的实现IPC的模块。

3.2共享内存的建立

补充:

1)管道就是文件,所以管道的管理使用的是文件的管理,不再需要OS另外去设计管理。

2)共享内存与管道不同,是一种新的东西,所以OS需要有另外一套管理方法(共享内存 + 内核描述结构)。

3)Linux会把物理内存划分为一个一个的内存页(Page:4096B —— 4KB),所以内存申请最好申请内存页的整数倍,否则OS会分配内存页向上取整的数量给用户,但是用户程序之恩那个使用自己申请的内存大小,会造成内存浪费。

3.2.1key

计算出OS中的唯一的key(因为物理地址也是需要唯一的)作为OS中的唯一值创建共享内存,尽量保证了共享内存创建不冲突。

pathname:一个可以自己自由定义的路径,最好自己有读写权限;

proj_id:8bit的非0值;

返回值:成功返回计算得到的key,失败返回-1;

 

注意:ftok不是系统调用,只是用来生成key的算法函数。

3.2.2申请内存(创建共享内存)

key:ftok计算拿到的key值;

size:共享内存的大小,最好为内存页的整数倍;

shmflg:获取共享内存的标志;

返回值:成功而则返回共享内存的id,失败则返回-1;

 

注意:共享内存如果程序结束后没有被删除,会一直存在于OS中,直到OS终止,这样就会导致shmget创建共享内存时失败。

1)shmflg

IPC_CREAT:单独使用时,如果共享内存已经存在,则获取它,否则创建共享内存;

—— 一般用来获取已经存在的共享内存。

IPC_CREAT|IPC_EXCL:同时使用时,如果已经存在则获取失败,如果不存在则创建共享内存;

—— 一般用来创建新的共享内存。

补充:

a、shmflg传入0时,默认是到哪都是用IPC_CREAT。

b、IPC_CREAT|IPC_EXCL创建共享内存时,还要给这段共享内存加上读写权限。

c、单独使用IPC_EXCL没有意义。

2)共享内存的ID和key

  • key:是为了保证不同进程使用相同的路径和proj_id通过相同的算法才能够计算出先沟通的key值,用于共享内存的创建或者获取。保证通信双方看到的是同一块共享内存。

  • ID:是用户层面的共享内存的唯一标识符(类似于文件描述符fd)。

3.2.3映射共享内存

shmid:共享内存的id,shmget的返回值;

shmaddr:需要映射到进程地址空间的哪个位置,一般传入nullptr即可,除非特殊需求;

shmflg:读写权限,默认使用0(可读可写);

返回值:映射成功则返回映射到的虚拟地址的起始地址,失败则返回空指针;

 

3.2.4取消映射共享内存

将已经映射到进程地址空间的共享内存取消映射。

shmaddr:共享内存的映射地址;

返回值:成功返回0,失败返回-1并设置errno;

3.2.5删除共享内存

1)为什么要删除共享内存?

共享内存不需要在使用了,将控制权返还给OS。

特别注意:共享内存的分配时OS级别的,所以如果共享内存没有被释放掉的话,程序结束了,共享内存也会一直存在,一直被OS认为是已经被分配了的共享内存,下一次运行程序在使用IPC_CREAT | IPC_EXCL来创建同一个key的共享内存的话会创建失败(因为已经存在),除非重新启动OS或者手动释放共享内存。

释放共享内存的方式:

a、重启OS让其自动释放。

b、使用命令手动释放。—— ipcxx系列命令。

c、程序内用完后释放。

补充:ipcxx系列命令是System V 和POSIX标准的进程间通信的管理工具。

查看 —— ipcs + 选项

删除 —— ipcrm + id

ipcs -m/-q/-s:查看进程间通信的共享内存/消息队列/信号量信息;

ipcrm -m/-q/-s id:通过ID删除进程间通信的共享内存/消息队列/信号量的系统资源;

ipcrm -M/-Q/-S key:通过关键字删除进程间通信的共享内存/消息队列/信号量的系统资源;

2)删除共享内存

shmid:需要删除的共享内存的id;

cmd:控制命令,删除使用IPC_RMID;

buff:指向共享内存描述结构体的指针,传入nullptr即可;

返回值:成功返回0,失败返回-1;

注意:

使用shmctl删除共享内存并不会立即直接将共享内存还给OS,令其他进程出现错误,而是将这一段共享内存标记为待删除状态,进程无法再shmat将这个共享内存映射到自己的进程地址空间,直到之前映射这一段共享内存的进程都取消映射shmdt了,映射计数器归0了,OS才会真正的回收这段内存。

补充:

1)shmctl接口本质是共享内存的控制系统调用,和fcntl是文件控制的系统调用类似。

2)cmd参数的补充:

总结:

1)只要是通信双方使用shm,一方直接向共享内存中吸入数据,另一方立马就可以看到数据,共享内存是所有进程间通信(IPC)速度最快的,因为不需要过多的拷贝(直接从键盘输入拷贝到内存)。

2)共享内存缺乏访问控制,会带来并发问题。

3)System V IPC资源必须删除,否则不会自动清除,除非重启,所以System V IPC资源的生命周期随内核。

4.System V 消息队列

内核中维护的一个链表或队列结构,每个节点存储一条带有类型标识的消息。

消息队列提供的是从一个进程向另一个进程发送一块数据的方法。也有类似于共享内存的接口(ftok、msgget、msgsnd、msgrcv、msgctl)。

System V标准实现的IPC与“Linux一切皆文件”思想不太适配,现代开发通常使用POSIX IPC。

5.System V 信号量

补充概念:

1)临界资源:多个执行流能够访问的公共资源。(在多个执行流场景中,为了保证安全,任何时间都只能被一个执行流访问的资源。)

2)临界区:访问临界资源的代码。

3)互斥:为了更好保护临界区,可以让多执行流在任何时刻都只能有一个执行流进入临界区。

4)原子性:一件事要么做,要么不做,不存在中间状态。

前面的IPC本质都是优先解决了让不同进程看到同一份资源的问题,但是也带来了时序问题,造成数据不一致,比如说共享内存。

在多执行流场景中,多个执行流运行起来会互相干扰,这时候访问临界资源就会很危险(非临界区没有影响)。

所以,每一个执行流想要进入临界区访问临界资源的一部分,不能让其直接去访问,要先申请信号量(本质是一个”计数器“,但是有一些封装),而且这个“计数器”(信号量)自身一定是OS提供的,否则”计数器“本身能够被多执行流访问的话,“计数器”也是临界资源,自身都不安全。

信号量:

1)申请信号量的本质:信号量计数器--。

2)申请信号量成功:临界资源内部,一定给申请的执行流预留了想要的资源(因为不一定申请了就立即使用)。

总结:

1)信号量本质是一个计数器。

2)信号量的申请本质是对临界资源的一种预定机制。

   
146   次浏览       6 次
相关文章

深度解析:清理烂代码
如何编写出拥抱变化的代码
重构-使代码更简洁优美
团队项目开发"编码规范"系列文章
相关文档

重构-改善既有代码的设计
软件重构v2
代码整洁之道
高质量编程规范
相关课程

基于HTML5客户端、Web端的应用开发
HTML 5+CSS 开发
嵌入式C高质量编程
C++高级编程

最新活动计划
AI大模型编写高质量代码 2-9[在线]
基于UML+EA进行分析设计 2-3[北京]
需求分析与管理 2-9[北京]
基于模型的数据治理 3-10[北京]
UAF与企业架构 2-3[北京]
ASPICE4.0核心开发过程 3-21[上海]
嵌入式软件测试 3-27[上海]
 
 
最新文章
.NET Core 3.0 正式公布:新特性详细解读
.NET Core部署中你不了解的框架依赖与独立部署
C# event线程安全
简析 .NET Core 构成体系
C#技术漫谈之垃圾回收机制(GC)
最新课程
.Net应用开发
C#高级开发技术
.NET 架构设计与调试优化
ASP.NET Core Web 开发
ASP.Net MVC框架原理与应用开发
成功案例
航天科工集团子公司 DotNet企业级应用设计与开发
日照港集 .NET Framewor
神华信 .NET单元测试
台达电子 .NET程序设计与开发
神华信息 .NET单元测试