1. 前言

上一小结谈到了操作系统中进程和线程的区别,其中进程之间、线程之间的通信方式不同,进程通信(Inter-Process Communication,简称 IPC)是指不同进程之间交换信息。操作系统中时刻都在进行 IPC,例如微信读取本地的文件,就是微信程序和文件系统进程交互的过程。

2. 进程间通信

面试官提问: 操作系统进程之间的通信方式有哪些?有什么特点?

题目解析:

操作系统中最常用的 IPC 方式有 5 种,分别是管道、命名管道、信号、共享内存以及套接字。

2.1 管道

管道(pipe),默认指无名管道。管道在两个进程之间建立一个通道,一个进程向这个通道写入字节流,另一个进程从这个通道读取字节流。用 C 语言描述管道示例:

#include <unistd.h>  // 引入linux头文件
int pipe(int fd[2]); // 返回:如果成功返回0,失败则返回-1 

上述定义的 fd 对象,其中 fd[0] 表示读文件描述符,f[1] 表示写文件描述符。

图片描述

管道的写和读操作

假设存在两个进程,分别为进程 A 和进程 B,那么进程 A 往 f[1] 写入,进程 B 则从自身的 f[0] 读取内容。

需要注意管道是半双工通信,也就是数据的流向是固定的,必须有一端是写入端,另一端是读取端。

2.2 信号

信号(Signal)是 Unix 系统中就已有的 IPC 方式,继承于 Unix 的 Linux 系统和 MacOS 系统也具有相同的通信方式。

信号的工作原理是向某个进程发送特定的消息,目标进程在收到消息之后,就知道特定事件已经发生,此时进程可以忽略消息即不做处理,或者是处理消息调用固定的函数。

以 MacOS 为例,在 shell 终端输入 kill -l 可以列出支出的全部信号名称:HUP INT QUIT ILL TRAP ABRT EMT FPE KILL BUS SEGV SYS PIPE ALRM TERM URG STOP

图片描述

MacOS 支持的信号列表

2.3 共享内存

共享内容(Shared Memory)是指两个进程之间可以读和写相同的操作系统内存空间,每个进程的操作对另外的进程都是可见的,这种通信方式非常类似线程之间的通信。

C 语言实现的共享内存步骤:

(1)shmget() :创建一段共享内存,或者引用已有的共享内存的空间;

(2)shmat() :连接已有的共享内存的地址;

(3)shmctl():建立连接之后,对共享内存进行读写操作;

(4)shmdt():所有操作都执行完成之后,断开连接。

图片描述

共享内存的操作模型

2.4 命名管道

命名管道(Named Pipe)实际上就是先进先出队列(First In First Out,简称 FIFO),候选人需要区分命名管道和管道,两者最大的区别在于管道只能在具有亲缘关系的两个进程之间通信,例如父子进程之间或者兄弟进程之间,命名管道则可以在任何两个进程之间通信,更加零活。

图片描述

命名管道的读写操作

如图所示,用户进程 A 是写入进程,写入的消息是 1 2 3 4 5,因为遵循先进先出的原则,用户进程 B 读出的消息顺序也是 1 2 3 4 5

2.5 套接字

上述介绍的 IPC 方式都是同一个主机内进程的交互方式,都是本地通信,套接字(Socket)一般用来处理不同主机进程之间的通信,也就是远程通信,是网络通信最常用的方式。Socket 通信需要 TCP 或者 UDP 协议的支持。使用 C 语言创建 Socket 的示例:

#include <sys/types.h> 
#include <sys/socket.h> //引入头文件
int socket(int domain, int type, int protocol); //创建一个socket

3. 小结

本章节介绍了 5 种最常见的进程间通信方式,候选人需要掌握没种通信方式的原理,最好能够画出原型图,而操作系统级别的通信一般不需要我们手动实现,有兴趣的同学可以了解下具体的实现,例如使用 Socket API 实现通信的编码方式,但是大部分实现接口并不会在面试中被考察,关注的重点在于定义。