说明:该系类文章更多的是从从哲学视角看 操作系统 这门学科。同时也是 操作系统的学习笔记总结。因为博主 这些年主要是以研究安卓系统和 嵌入式Linux为主,因此这个系类文章也是这两个领域不可或缺的基石之一,尤其是对操作系统感兴趣的伙伴可特别关注。


6 进程通信

6.1 进程为什么要通信

通信是人的需求,对于人发明的进程,自然脱离不了人的习性;而且,如果进程间不能通信,那么所能完成的任务也会大打折扣。进程间的交互称为进程间通信(Inter Process Communication)IPC。所有进程间通信的方式都是源于生活、源于人通信方式的抽象。

6.2 进程对白:管道、记名管道、套接字

人们最常用的通信手段就是对白,即一个人发出声音,另一个接收声音;而声音本身可以通过各种方式传递。与此类似,进程对白就是一个进程发出某种数据信息,另一方接收数据信息,而这些数据信息通过一片共享的存储空间进行传递。这种方式类似于“管道”,一个口进,一个口出;在系统上创建一个管道操作系统需要做的就是在某种存储介质上划出一片空间,赋给其中一个进程写的权限,另一个进程读的权限。

6.2.1 管道

从根本上说,管道是一个线性字节数组,类似文件,但是在文件系统中却看不到它。

  • 创建一个管道使用系统调用popen()/pipe()。popen需要提供一个目标进程作为参数,然后在调用该函数的进程和给出的目标进程之间创建一个管道。这很像打电话需要提供对方的电话号码一样;同时创建的时候需要提供一个参数表明管道类型:读管道/写管道;pipe()函调用将返回两个文件描述符,保证一个进程读管道,另一个进程写管道。
  • 通常情况下是这样,使用pipe()创建管道,再fork产生两个进程,这两个进程使用pipe()返回的两个文件描述符进行通信。

管道的一个重要特点是使用管道的两个进程之间必须要有某种关系,例如父子进程,或者同一个父进程的子进程。

6.2.2 记名管道

对于两个不相关的进程之间进行管道通信,就需要使用记名管道,记名管道与文件系统共享一个名字空间,所以在文件系统中可以看到记名管道。

  • 创建一个记名管道后,其他进程可以通过open打开这个管道,从而与另一端进行交流。    
  • 记名管道的名称由两个部分组成,计算机名+管道名。对于同一台主机来说,允许同一个管道被不同的进程打开,但是不同的管道都有属于自己的管道缓冲区而且有自己的通信环境,互不影响。命名管道可以支持多个客户端连接一个服务器。命名管道客户端不但可以与主机上的服务器通信,还可以同其他主机上的服务器进行通信。

管道虽然简单,好用,但是其缺点也很明显,那就是不是所有的操作系统都支持;因此,如果在多平台的环境下,管道机制就会多半力不从心了。

6.2.3 虫洞,套接字

首先是在BSD操作系统中出现,随后渗入到各种操作系统中。套接字的功能很强大,可以支持不同层面,不同应用,跨网络之间的通信。

使用套接字:双方均创建一个套接字,一个是服务器端,一个是客户端;服务器端创建一个服务区套接字,在该套接字上面进行监听,等到远方的连接请求;客户端需要创建一个客户端套接字,向服务器端套接字发送请求,服务器端在收到连接请求后创建一个客户端套接字,与远方的客户机上的客户端连接形成点到点的通信通道;之后两者通过send和recv的方式进行通信。

套接字流程简图如下:

操作系统哲学原理(06)进程原理-进程通信_共享内存

同时这里以TCP为例,详细解读下该套接字流程,如下所示:

操作系统哲学原理(06)进程原理-进程通信_消息队列_02

套接字因为功能强大而广泛使用,不同的操作系统均支持/实现某种套接字功能。按照传输媒介,套接字分为:本地套接字、网域套接字。而网域套接字按照其提供的数据传输特性分为几大类:

  • 数据流套接字:提供双向、有序、可靠、非重复数据通信。
  • 电报流套接字:提供双向消息流。数据不一定按序到达。
  • 序列包套接字:提供双向、有序、可靠连接、包有最大限制。
  • 裸套接字:提供对下层协议的访问。

套接字很复杂,每种操作系统对其处理完全不一样。

6.3 进程电报:信号

虽然管道,套接字广泛运用,对于大批量数据通信这是好方法,但是数据量很小的任务如果用管道和套接字,需要建立连接,而这需要消耗系统资源,这时候用它们有点“杀鸡焉用牛刀”了。因此我们需要一种不同的机制来处理如下通信请求:

  • 迫使一方对我们的通信立即做出回应。
  • 不想事先建立任何连接,而是突然觉得有必要与某个进程通信。
  • 传输的信息量小,使用管道和套接字不划算。

于是我们想到了信号,它源于生活中电报、信的机制。信号是一个内核结构,发送方将数据结构中的内容填好,并指明该信号的目标进程后,发出特定的软中断;操作系统接收到特定的软中断请求以后,会到特定的内核数据结构里查找信号接收方,并进行通知。接到通知的进程会对信号进行相应处理。

6.4 进程旗语:信号量

信号量由荷兰人构思出来的一种程序设计构造。其原型源于铁路的运行。即一条单轨上一次只能由一列列车行驶在上面,而管理铁路的这个系统就是信号量。在计算机中,信号量就是一个简单的整数。一个进程在信号变为0/1的情况下推进,并且将信号变为1/0来防止别的进程推进。当进程完成任务的时候则将信号再变为0/1,从而允许其他进程执行。

注意:信号量不只是一种通信机制,还是一种同步机制。

6.5 进程拥抱:共享内存

  • 对于大量数据的共享,前面几种机制显然还不可以,所以出现了共享内存这种机制;即两个进程共同拥有一片内存,二者均可访问;要使用共享内存,首先需要一个进程创建它 ,之后其他进程就将该片内存映射到自己的虚拟空间即可读取其中内容,从而进行通信。
  • 共享内存与管道不同,它的访问时随机的,不是一个进程读,另一个进程写;因此其灵活性很大,能够传递的信息也很多。共享内存也有缺陷,共享内存的两个进程必须运行在同一台物理机器上,而且管理复杂;另一个缺陷就是安全性脆弱,因为两个进程存在于一片共享内存,如果一个进程中毒,另一个也逃不掉。

注意:使用全局变量在同一个进程的进程间实现通信不称为共享内存。

6.6 信件发送:消息队列

消息队列也是不同于管道的,他不是一头读,一头写;而是无需固定的读写进程,任何进程都可以读写,其次,他可以支持多个进程,多个进程可以读写消息队列,不是点对点,而是多对多。

注意:消息队列只在内存中实现。同时几乎所有的操作系统全都支持它,并不只是在linux/unix下实现。

6.7 其他通信机制

通信机制虽然很多,而且每一种都有自己的特性;但是归根结底都是源于AT&T的unix V系统。在1983年加入了共享内存、信号量和消息队列,这三者就是SYSTEM V IPC(POSIX 也是源于此系统并且称为当前的IPC标准)。因此不同操作系统的IPC机制不尽相同,但是其基本原理并没有大的差别。。