﻿---
title: Inter Process Communication
date: 2024-03-11
excerpt: IPC, Interprocess Communication
tags: [OS, IPC]
thumbnail: https://assets.vluv.space/cover/OS/Ch2-3IoC.webp
cover: https://assets.vluv.space/cover/OS/Ch2-3IoC.webp
---

<script data-swup-reload-script type="module" src="/js/components/tab.js"></script>

## 进程通信的类型 Types Of IPC

### Overview

**低级通信**：进程之间的互斥和同步，由于其所交换的信息量少而被归结为低级通信。
信号量机制作为同步工具是卓有成效的，但作为通信工具，则不够理想，
主要表现在下述两方面：

1. 效率低，生产者每次只能向缓冲池投放一个产品(消息)，消费者每次只能从缓冲区中取得一个消息；
2. 通信对用户不透明。

---

**高级通信**：是指用户可直接利用操作系统所提供的一组通信命令高效地传送大量数据的一种通信方式。常用的高级通信方式有 :

- **共享存储器系统**：在内存中分配一片空间作为共享存储区
- **管道通信**：写者向管道文件中写入数据；读者从该文件中读出数据
- **消息传递系统**：以消息（Message）为单位在进程间进行数据交换
  - 直接通信方式
  - 间接通信方式
- **客户机-服务器系统**

### 共享存储器系统 Shared-Memory System

1. **基于共享数据结构的通信方式**
   诸进程共用某些数据结构，借以实现诸进程间的信息交换。
   如在生产者—消费者问题中，就是用有界缓冲区这种数据结构来实现通信的。这种通信方式是低效的，只适于传递相对少量的数据。

2. **基于共享存储区的通信方式**
   由操作系统在内存中划分出一块区域作为共享存储区。
   进程在通信前向操作系统申请共享存储区中的一个分区。
   然后，申请进程把获得的共享存储分区连接到本进程上，此后便可象读/写普通存储器一样地读/写共享存储分区。
   该方式下，通信进程之间的同步与互斥访问共享存储区由进程负责。

### 管道通信 Pipe Communication

所谓“管道”，是指用于连接一个读进程和一个写进程以实现他们之间通信的一个共享文件，又名 pipe 文件。
向管道(共享文件)提供输入的发送进程 Writer， 以**字符流 Character Stream**形式将大量的数据写入管道；
接受管道输出的接收进程 Reader，则从管道中读数据。由于发送进程和接收进程是利用管道进行通信的，故又称为管道通信。
这种方式首创于 UNIX 系统，由于它能有效地传送大量数据，因而又被引入到许多其它操作系统中。

为了协调双方的通信，管道机制必须提供以下**三方面**的协调能力：

- **互斥**，即当一个进程正在对 pipe 执行读/写操作时，其它(另一)进程必须等待。
- **同步**，指当写(输入)进程把一定数量(如 4KB)的数据写入 pipe，便去睡眠等待， 直到读(输出)进程取走数据后，再把他唤醒。当读进程读空 pipe 时，也应睡眠等待，直至写进程将数据写入管道后，才将之唤醒。
- **确定对方是否存在**，只有确定了对方已存在时，才能进行通信。

Linux 命名管道非常适合同一机器上两个进程之间传递数据，其形式也是一个文件，但是读取与写入时遵循 FIFO 的原则。

```c
#define FIFO_NAME "/tmp/my_fifo"
#define BUFFER_SIZE PIPE_BUF
mkfifo(FIFO_NAME,0777);
```

### 消息传递系统 Message-Passing System

应用最广泛的进程通信机制,进程间数据交换以格式化的信息 Message 为单位
程序员使用通信原语完成通信，其实现细节被隐藏，简化了通信程序的编写复杂性
微内核与服务器程序的通信采用本机制，可满足多处理机 OS、分布式 OS、计算机网络的通信要求

**消息传递通信的实现方法**

- **直接通信方式** `send(),receive()`
- **间接通信方式** 信箱

#### 直接通信方式

指发送进程利用 OS 提供的发送命令,直接把消息发送给目标进程。通常，系统提供下述两种类型的通信命令(原语)：

- **对称寻址方式 Symmetric Addressing**
  `Send(Receiver, Message);`
  `Receive(Sender, Message);`
  例如，原语`Send(P2, m1)`表示将消息 m1 发送给接收进程 P2; 而原语`Receive(P1，m1)`则表示接收由 P1 发来的消息 m1
- **非对称寻址方式 Asymmetric Addressing**
  `Send(P, Message);`发送一个消息给接收进程 P；
  `Receive(id,Message);`接收来自任何进程的消息，进程 id 不固定

利用直接通信原语，来解决生产者-消费者问题：
当生产者生产出一个产品(消息)后，便用 Send 原语将消息发送给消费者进程；而消费者进程则利用 Receive 原语来得到一个消息。如果消息尚未生产出来，消费者必须等待，直至生产者进程将消息发送过来

```pascal
repeat
     …
    produce an item in nextp;
      …
    send(consumer, nextp);
   until false;

repeat
    receive(producer, nextc);
      …
    consume the item in nextc;
  until false;
```

**消息缓冲队列通信机制**

发送进程利用 `send` 原语将消息直接发送给接收进程的消息缓冲队列；
接收进程利用 `receive` 原语接收消息；
用于本地进程通信。

![](https://assets.vluv.space/UESTC/OS/Ch2-3IPC/Ch2-3InterprocessCommunication-2024-03-11-13-21-04.webp)

**Send**

- **发送进程**在利用发送原语发送消息之前，应先在自己的内存空间，设置一发送区`a`，把待发送的消息正文、发送进程标识符、消息长度等信息填入其中，然后调用发送原语，把消息发送给目标(接收)进程。
- 发送原语首先根据发送区`a`中所设置的消息长度`a.size`来申请一缓冲区`i`，接着，把发送区`a`中的信息复制到缓冲区`i`中。为了能将`i`挂在**接收进程的消息队列**`mq`上，应先获得**接收进程**的内部标识符`j`，然后将`i`挂在`j.mq`上。
- 由于该队列属于临界资源, 故在执行`insert`操作的前后，需要执行`wait`和`signal`操作

**Receive**

- **接收进程**调用接收原语`receive(b)`，从自己的消息队列`mq`中,摘下第一个消息缓冲区 i,并将其中的数据复制到以 b 为首址的指定消息接收区内
- 完成消息的接收后接收进程返回到用户态继续进行

![](https://assets.vluv.space/UESTC/OS/Ch2-3IPC/Ch2-3InterprocessCommunication-2024-03-11-13-24-54.webp)

#### 消息传递系统实现中的若干问题

- **通信链路(communication link)**
  为使在发送进程和接收进程之间能进行通信，必须在两者之间建立一条通信链路。有两种方式建立通信链路。

  - 第一种方式：由发送进程在通信之前，用显式的“建立连接”命令(原语)请求系统为之建立一条通信链路；在链路使用完后，也用显式方式拆除链路。这种方式主要用于计算机网络中
  - 第二种方式: 发送进程无须明确提出建立链路的请求，只须利用系统提供的发送命令(原语)，系统会自动地为之建立一条链路。这种方式主要用于单机系统中。

  根据通信链路的**连接方法**，又可把通信链路分为两类：

  - **点对点连接通信链路**，这时的一条链路只连接两个结点(进程)；
  - **多点连接链路**，指用一条链路连接多个(n ＞ 2)结点(进程)。
    而根据**通信方式**的不同，则又可把链路分成两种：
  - **单向通信链路**，只允许发送进程向接收进程发送消息；
  - **双向链路**，既允许由进程 A 向进程 B 发送消息，也允许进程 B 同时向进程 A 发送消息。

- **消息的格式**
  - **定长消息**
    在某些 OS 中，消息是采用比较短的定长消息格式，这减少了对消息的处理和存储开销。
  - **变长消息**
    在有的 OS 中，采用另一种变长的消息格式，即进程所发送消息的长度是可变的。系统在处理和存储变长消息时，须付出更多的开销，但方便了用户。
    注意：这两种消息格式各有其优缺点，故在很多系统(包括计算机网络)中，是同时都用的。
- **进程同步方式**
  进程间通过消息队列通信，则进程之间需同步，其同步方式有三种：
  - **发送进程阻塞,接收进程阻塞**：进程间汇合同步，有消息时传递，无消息时同时阻塞。
  - **发送进程不阻塞,接收进程阻塞**：发送者尽快发送消息，接收者平时阻塞收到消息才唤醒，例如多个用户共享一个打印服务。
  - **发送进程和接收进程均不阻塞**：发送者和接收者均忙于自身事务，直到无法继续才阻塞。例如发送进程和接收进程间关联一个长度为 n 的消息队列。

#### 间接通信方式

信箱是间接通信方式的一种实现，是一种特殊的消息队列，用于进程间通信。

**信箱的创建和撤消**:进程可利用信箱创建原语来建立一个新信箱。创建者进程应给出信箱名字、信箱属性(公用、私用或共享)；对于共享信箱，还应给出共享者的名字。当进程不再需要读信箱时，可用信箱撤消原语将之撤消。
**消息的发送和接收**:当进程之间要利用信箱进行通信时，必须使用共享信箱，并利用系统提供的下述通信原语进行通信。
`Send(mailbox, message);` 将一个消息发送到指定信箱；
`Receive(mailbox, message);` 从指定信箱中接收一个消息；

信箱定义为一种数据结构。在逻辑上，可以将其分为信箱头 MailboxHeader 和信箱体 MailboxBody 两部分

![](https://assets.vluv.space/UESTC/OS/Ch2-3IPC/Ch2-3InterprocessCommunication-2024-03-11-14-00-40.webp)

信箱可由操作系统创建，也可由用户进程创建，创建者是信箱的拥有者。据此，可把信箱分为以下三类：

- **私用信箱** 用户**进程**可为自己建立一个新信箱，并作为该进程的一部分。信箱的拥有者有权从信箱中读取消息，其他用户则只能将自己构成的消息发送到该信箱中。这种私用信箱可采用单向通信链路的信箱来实现。当拥有该信箱的进程结束时，信箱也随之消失。
- **公用信箱** 由**操作系统**创建，并提供给系统中的所有核准进程使用。进程既可把消息发送到该信箱中，也可从信箱中读取发送给自己的消息。显然，公用信箱应采用双向通信链路的信箱来实现。通常，公用信箱在系统运行期间始终存在。
- **共享信箱** 由某**进程**创建，在创建时或创建后，指明它是可共享的，同时须指出共享进程(用户)的名字。信箱的拥有者和共享者，都有权从信箱中取走发送给自己的消息。

在利用信箱通信时，在发送进程和接收进程之间，存在以下四种关系：

- **一对一关系** 这时可为发送进程和接收进程建立一条两者专用的通信链路，使两者之间的交互不受其他进程的干扰
- **多对一关系** 允许提供服务的进程与多个用户进程之间进行交互，也称为客户/服务器交互(client/server interaction)
- **一对多关系** 允许一个发送进程与多个接收进程进行交互，使发送进程可用广播方式，向接收者(多个)发送消息
- **多对多关系**。允许建立一个公用信箱，让多个进程都能向信箱中投递消息；也可从信箱中取走属于自己的消息

### 客户机-服务器系统 Client-Server System

#### 套接字 Socket

**Socket 定义**
Windows Sockets 是**为上层应用程序提供的一种标准网络接口**，主要用于网络中的数据通信。上层应用程序不用关心 Winsock 的实现细节，它为上层应用程序提供透明的服务。
Windows Sockets 规范以 U.C. Berkeley 大学 BSD UNIX 中流行的 Socket 接口为范例定义了一套 microsoft Windows 下网络编程接口。
它不仅包含了人们所熟悉的 Berkeley Socket 风格的库函数；也包含了一组针对 Windows 的扩展库函数，以使程序员能充分地利用 Windows 消息驱动机制进行编程。

<center>

<img src="https://assets.vluv.space/UESTC/OS/Ch2-3IPC/Ch2-3InterprocessCommunication-2024-03-11-14-18-48.webp" style="width:50%;" />

</center>

**TCP/IP**

TCP/IP 协议的核心部分是传输层协议(TCP、UDP)，网络层协议(IP)和物理接口层，这三层通常是在操作系统内核中实现。因此用户一般不涉及。
编程时，编程界面有两种形式：由内核直接提供的系统调用；使用以库函数方式提供的各种函数。前者为核内实现，后者为核外实现。用户服务要通过核外的应用程序才能实现，所以要使用套接字(socket)来实现。

**TCP/IP 协议与 WinSock 的关系**

WinSock 不是一种网络协议，它只是一个网络编程接口，可以把它当作一些协议的封装。
WinSock 就是 TCP/IP 协议的一种封装，可以通过调用 WinSock 的接口函数来调用 TCP/IP 的各种功能.
例如想用 TCP/IP 协议发送数据，就可以使用 WinSock 的接口函数 Send()来调用 TCP/IP 的发送数据功能，至于具体怎么发送数据，WinSock 已经帮用户封装好了这种功能。

<x-tabs>

<x-tab title="TCP" active>

![TCP](https://assets.vluv.space/UESTC/OS/Ch2-3IPC/Ch2-3InterprocessCommunication-2024-03-11-14-20-43.webp)

</x-tab>

<x-tab title="UDP">

![UDP](https://assets.vluv.space/UESTC/OS/Ch2-3IPC/Ch2-3InterprocessCommunication-2024-03-11-14-28-31.webp)

  </x-tab>

</x-tabs>

#### 远程过程调用 RPC

**远程过程调用 RPC**(Remote Procedure Call)，是一个通信协议，用于通过网络连接的系统。该协议允许运行于一台主机(本地)系统上的进程调用另一台主机(远程)系统上的进程，而对程序员表现为常规的过程调用，无需额外地为此编程。如果涉及的软件采用面向对象编程，那么远程过程调用亦可称做远程方法调用。
RPC 主要是为了解决的两个问题：

- 解决分布式系统中，服务之间的调用问题。
- 远程调用时，要能够像本地调用一样方便，让调用者感知不到远程调用的逻辑

**Steps**

1. 调用客户端存根；执行传送参数
2. 调用本地系统内核发送网络消息
3. 消息传送到远程主机
4. 服务器存根得到消息并取出参数
5. 执行远程过程
6. 执行的过程将结果返回服务器存根
7. 服务器存根封装结果，调用远程系统内核
8. 消息传回本地主机
9. 客户存根从内核接收消息
10. 客户接收存根返回的数据

![Ch2-3InterprocessCommunication-2024-03-11-14-51-43](https://assets.vluv.space/UESTC/OS/Ch2-3IPC/Ch2-3InterprocessCommunication-2024-03-11-14-51-43.webp)
