进程通信方式
大约 8 分钟
进程通信方式
六种进程通信
六种方式1
- 套接字 Socket:套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
- 管道 pipe:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
- 命名管道 FIFO:有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
- 消息队列 MessageQueue:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
- 共享存储 SharedMemory:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
- 信号量 Semaphore:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
- 信号 Sinal : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
六种方式2
- 匿名管道
- 缺点1:效率低、无格式、单向。通信的数据是无格式的流并且大小受限,通信的方式是单向的,数据只能在一个方向上流动,如果要双向通信,需要创建两个管道
- 缺点2:需亲缘关系。匿名管道是只能用于存在父子关系的进程间通信,匿名管道的生命周期随着进程创建而建立,随着进程终止而消失。
- 本质:内核缓存
- 命名管道
- 改进1:非亲缘。突破了匿名管道只能在亲缘关系进程间的通信限制,因为使用命名管道的前提,需要在文件系统创建一个类型为 p 的设备文件,那么毫无关系的进程就可以通过这个设备文件进行通信。
- 本质:内核缓存、先进先出。另外,不管是匿名管道还是命名管道,进程写入的数据都是缓存在内核中,另一个进程读取数据时候自然也是从内核中获取,同时通信数据都遵循先进先出原则,不支持 lseek 之类的文件定位操作。
- 消息队列
- 改进1:有格式。克服了管道通信的数据是无格式的字节流的问题,消息队列实际上是保存在内核的「消息链表」,消息队列的消息体是可以用户自定义的数据类型,发送数据时,会被分成一个一个独立的消息体,当然接收数据时,也要与发送方发送的消息体的数据类型保持一致,这样才能保证读取的数据是正确的。
- 缺点:有用户内核态切换损耗。消息队列通信的速度不是最及时的,毕竟每次数据的写入和读取都需要经过用户态与内核态之间的拷贝过程。
- 本质:内核的消息链表
- 共享内存
- 改进:无用户内核态切换损耗。可以解决消息队列通信中用户态与内核态之间数据拷贝过程带来的开销,它直接分配一个共享空间,每个进程都可以直接访问,就像访问进程自己的空间一样快捷方便,不需要陷入内核态或者系统调用,大大提高了通信的速度,享有最快的进程间通信方式之名。
- 缺点:进程竞争负担。但是便捷高效的共享内存通信,带来新的问题,多进程竞争同个共享资源会造成数据的错乱。
- 本质:两个虚拟内存的某段映射到同一物理空间
- 信号量
- 改进1:互斥进程竞争。那么,就需要信号量来保护共享资源,以确保任何时刻只能有一个进程访问共享资源,这种方式就是互斥访问。
- 新功能:可以进行互斥和同步。信号量不仅可以实现访问的互斥性,还可以实现进程间的同步,信号量其实是一个计数器,表示的是资源个数,其值可以通过两个原子操作来控制,分别是 P 操作和 V 操作
- 本质:整型的计数器
- 信号
- 特点:唯一的异步通信机制。进程间通信机制中唯一的异步通信机制
- Socket
- 特点:能跨主机通信。前面说到的通信机制,都是工作于同一台主机,如果要与不同主机的进程间通信,那么就需要 Socket 通信了。
总结与比较六种通信方式
共同点
每个进程的用户地址空间都是独立的,一般而言是不能互相访问的,但内核空间是每个进程都共享的,所以进程之间要通信必须通过内核。
上面六种进程通信方式都是通过Linux内核来提供的
选用
- 匿名管道:简单,单向,无格式字节流
- 命名管道:Super:可不亲缘
- 信息队列:Super:有格式
- 共享内存:Super:
- 不同主机
- Socket
线程通信
以上,就是进程间通信的主要机制了。你可能会问了,那线程通信间的方式呢?
同个进程下的线程之间都是共享进程的资源,只要是共享变量都可以做到线程间通信,比如全局变量。
所以对于线程间关注的不是通信方式,而是关注多线程竞争共享资源的问题,信号量也同样可以在线程间实现互斥与同步:
- 互斥的方式,可保证任意时刻只有一个线程访问共享资源;
- 同步的方式,可保证线程 A 应在线程 B 之前执行;
(旧笔记)
信号
Linux提供了几十种信号,分别代表着不同的意义。信号之间依靠他们的值来区分,但是通常在程序中使用信号的名字来表示一个信号。在Linux系统中,这些信号和以他们的名称命名的常量被定义在/usr/includebitssignum.h文件中。通常程序中直接包含 <signal.h>
就好。
信号事件有两个来源:
- 硬件来源,例如按下了cltr+C,通常产生中断信号sigint
- 软件来源,例如使用系统调用或者命令发出信号。最常用的发送信号的系统函数是kill,raise,setitimer,sigation,sigqueue函数。软件来源还包括一些非法运算等操作。
一旦有信号产生,用户进程对信号产生的相应有三种方式:
- 执行默认操作,linux对每种信号都规定了默认操作。
- 捕捉信号,定义信号处理函数,当信号发生时,执行相应的处理函数。
- 忽略信号,当不希望接收到的信号对进程的执行产生影响,而让进程继续执行时,可以忽略该信号,即不对信号进程作任何处理。 有两个信号是应用进程无法捕捉和忽略的,即SIGKILL和SEGSTOP,这是为了使系统管理员能在任何时候中断或结束某一特定的进程。
管道
管道允许在进程之间按先进先出的方式传送数据,是进程间通信的一种常见方式。
管道是Linux 支持的最初Unix IPC形式之一,具有以下特点:
- 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道;
- 匿名管道只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程);
- 单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中。
无名管道
命名管道
消息队列
消息队列,就是一个消息的链表,是一系列保存在内核中消息的列表。用户进程可以向消息队列添加消息,也可以向消息队列读取消息。
消息队列与管道通信相比,其优势是对每个消息指定特定的消息类型,接收的时候不需要按照队列次序,而是可以根据自定义条件接收特定类型的消息。
共享内存
内存映射
链接到当前文件 0
没有文件链接到当前文件