进程间通信方式(管道、消息队列、内存映射区、共享内存等)

进程间通信 (Inter-Process Communication, 简写为 IPC) 是两个进程之间进行信息交流的一种机制, 不仅仅会发生在同一主机的两个进程之间, 也可以发生在不同主机的两个进程之间,。

在一台主机上,每个进程的用户地址空间都是独立的,一般而言是不能互相访问的,但内核空间是每个进程都共享的,所以进程之间要通信必须通过内核。Linux内核提供了很多通信方式:管道、消息队列、内存映射区、共享内存、套接字。套接字主要应用于跨网络不同主机间的通信(也可以同一主机)。

1、管道与消息队列

1、管道

管道可以分为匿名管道与有名管道。匿名管道只适合于有血缘关系的进程;有名管道也适合于没有血缘关系的进程。

管道的本质其实就是内核中的一块内存 (或者叫内核缓冲区),这块缓冲区中的数据存储在一个环形队列中,因为管道在内核里边,因此我们不能直接对其进行任何操作。Linux中一切都是文件,管道也是文件,读写管道的函数就是 Linux 中的文件 IO 函数。传输的是无格式的字节流信息。

 

2、消息队列

消息队列是内核基于链表实现的数据结构,也在内核中

向消息队列中写数据,实际上是向这个数据结构中插入一个新结点;从消息队列汇总读数据,实际上是从这个数据结构中删除一个结点,消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法。

3、区别

  • 实现通信的方式:

管道与消息队列都对应的是内核区,管道是无格式的字节流,消息队列可以是自定义的数据结构;

  • 生命周期

消息队列生命周期随内核,如果没有释放消息队列或者没有关闭操作系统,消息队列会一直存在,匿名管道的生命周期,是随进程的创建而建立,随进程的结束而销毁。

  • 效率

消息队列与管道,通信过程中,因为都在内核态,所以会存在内核态与用户态的切换开销。

2、内存映射区与共享内存

管道、消息队列对应的内存空间都在内核区,会带来用户态与内核态的切换开销。内存映射区与共享内存这两种方法,是在用户区解决通信的方式。

1、内存映射区

内存映射区对应的内存空间是用户区(用于加载动态库的那块区域),也就是说进程间通信使用的内存映射区不是一块,而是在每个进程中都有一块。由于每个进程的地址空间都是独立的,各个进程之间也不能访问对方的内存映射区,需要通信的进程需要将各自的内存映射区和同一个磁盘文件进行映射,这样进程之间就可以通过这个唯一的磁盘文件进行信息交互了。

内存映射区既适合有血缘关系的进程,也适合没有血缘关系的进程,使用一套API。

 

 

2、共享内存

使用共享内存,需要将进程与共享内存联系,得到共享内存的起始地址之后就可以直接进行读写操作了,进程也可以和这块共享内存解除关联,解除关联之后就不能操作这块共享内存了。在所有进程间通信的方式中共享内存的效率是最高的。

 

3、区别

  • 实现进程间通信的方式

shm: 多个进程只需要一块共享内存就够了,共享内存不属于进程,需要和进程关联才能使用
内存映射区:位于每个进程的虚拟地址空间中,并且需要关联同一个磁盘文件才能实现进程间数据通信

  • 效率:

shm: 直接对内存操作,效率高
内存映射区:需要内存和文件之间的数据同步,效率低

  • 生命周期

内存映射区:进程退出,内存映射区也就没有了
shm:进程退出对共享内存没有影响,调用相关函数 / 命令 / 关机才能删除共享内存

  • 数据的完整性 -> 突发状态下数据能不能被保存下来(比如:突然断电)

内存映射区:可以完整的保存数据,内存映射区数据会同步到磁盘文件
shm:数据存储在物理内存中,断电之后系统关闭,内存数据也就丢失了

4、信号量

共享内存由于多个进程都可以同时使用,就会牵扯到信息同步的问题,信号量其实是一个整型的计数器,主要用于实现进程间的互斥与同步,而不是用于缓存进程间通信的数据

3、信号

上文所说的,都是进程间同步通信的方式。信号是一种进程间异步通信的方式,主要用于终止、挂起等控制信息的传递。

4、socket

主要用于不同主机间的网络通信。


链接: https://subingwen.cn/linux/shm/

摘抄自:公众号:小林coding
总结:

由于每个进程的用户空间都是独立的,不能相互访问,这时就需要借助内核空间来实现进程间通信,原因很简单,每个进程都是共享一个内核空间。

Linux 内核提供了不少进程间通信的方式,其中最简单的方式就是管道,管道分为「匿名管道」和「命名管道」。

匿名管道顾名思义,它没有名字标识,匿名管道是特殊文件只存在于内存,没有存在于文件系统中,shell 命令中的「|」竖线就是匿名管道,通信的数据是无格式的流并且大小受限,通信的方式是单向的,数据只能在一个方向上流动,如果要双向通信,需要创建两个管道,再来匿名管道是只能用于存在父子关系的进程间通信,匿名管道的生命周期随着进程创建而建立,随着进程终止而消失。

命名管道突破了匿名管道只能在亲缘关系进程间的通信限制,因为使用命名管道的前提,需要在文件系统创建一个类型为 p 的设备文件,那么毫无关系的进程就可以通过这个设备文件进行通信。另外,不管是匿名管道还是命名管道,进程写入的数据都是缓存在内核中,另一个进程读取数据时候自然也是从内核中获取,同时通信数据都遵循先进先出原则,不支持 lseek 之类的文件定位操作。

消息队列克服了管道通信的数据是无格式的字节流的问题,消息队列实际上是保存在内核的「消息链表」,消息队列的消息体是可以用户自定义的数据类型,发送数据时,会被分成一个一个独立的消息体,当然接收数据时,也要与发送方发送的消息体的数据类型保持一致,这样才能保证读取的数据是正确的。消息队列通信的速度不是最及时的,毕竟每次数据的写入和读取都需要经过用户态与内核态之间的拷贝过程。

共享内存可以解决消息队列通信中用户态与内核态之间数据拷贝过程带来的开销,它直接分配一个共享空间,每个进程都可以直接访问,就像访问进程自己的空间一样快捷方便,不需要陷入内核态或者系统调用,大大提高了通信的速度,享有最快的进程间通信方式之名。但是便捷高效的共享内存通信,带来新的问题,多进程竞争同个共享资源会造成数据的错乱。

那么,就需要信号量来保护共享资源,以确保任何时刻只能有一个进程访问共享资源,这种方式就是互斥访问。信号量不仅可以实现访问的互斥性,还可以实现进程间的同步,信号量其实是一个计数器,表示的是资源个数,其值可以通过两个原子操作来控制,分别是 P 操作和 V 操作

与信号量名字很相似的叫信号,它俩名字虽然相似,但功能一点儿都不一样。信号是进程间通信机制中唯一的异步通信机制,信号可以在应用进程和内核之间直接交互,内核也可以利用信号来通知用户空间的进程发生了哪些系统事件,信号事件的来源主要有硬件来源(如键盘 Cltr+C )和软件来源(如 kill 命令),一旦有信号发生,进程有三种方式响应信号 1. 执行默认操作、2. 捕捉信号、3. 忽略信号。有两个信号是应用进程无法捕捉和忽略的,即 SIGKILL 和 SEGSTOP,这是为了方便我们能在任何时候结束或停止某个进程。

前面说到的通信机制,都是工作于同一台主机,如果要与不同主机的进程间通信,那么就需要 Socket 通信了。Socket 实际上不仅用于不同的主机进程间通信,还可以用于本地主机进程间通信,可根据创建 Socket 的类型不同,分为三种常见的通信方式,一个是基于 TCP 协议的通信方式,一个是基于 UDP 协议的通信方式,一个是本地进程间通信方式。

以上,就是进程间通信的主要机制了。你可能会问了,那线程通信间的方式呢?

同个进程下的线程之间都是共享进程的资源,只要是共享变量都可以做到线程间通信,比如全局变量,所以对于线程间关注的不是通信方式,而是关注多线程竞争共享资源的问题,信号量也同样可以在线程间实现互斥与同步:

  • 互斥的方式,可保证任意时刻只有一个线程访问共享资源;

  • 同步的方式,可保证线程 A 应在线程 B 之前执行;

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值