2.6 进程通信

L11

学习目标

进程通信用消息队列传递数据;本课对照 LiteOS-M 的 LOS_Queue,在内核里实现发送/接收与阻塞唤醒。

  • 直接通信与间接通信(信箱/队列)是高级通信的两类实现。
  • 消息队列:发送方把消息写入队列,接收方读出;队列满/空时任务阻塞,由内核唤醒。
  • MCU 上多任务共享地址空间,仍可用队列做解耦,避免直接共享可变缓冲。
  • 本课重点阅读 kernel/los_queue.c:Create / Write / Read 与等待队列。

原理 · 深入理解

2.6 进程通信

本节概览:先建立「进程通信」的框架,再依次展开下列小节。

  1. 进程通信的类型
  2. 消息传递通信的实现方式
  3. 直接消息传递系统实例

进程间传送大量数据时,应利用 OS 提供的高级通信工具。

  1. 高效地传送大量数据:用户可直接利用高级通信命令高效传送
  2. 使用方便:OS 隐藏通信细节,提供一组通信原语,通信过程对用户透明

2.6.1 进程通信的类型

消息传递系统中,进程不再借助共享存储区或数据结构,而是以格式化的消息(message)为单位,把数据封装在消息中,通过 OS 提供的通信原语在进程间传递,完成数据交换。

它属于高级通信方式,按实现方式不同分为两类。

  1. 直接通信方式
  2. 间接通信方式

管道(pipe)是用于连接一个写进程和一个读进程以实现通信的共享文件,又称 pipe 文件。

写进程以字符流形式把大量数据送入管道,读进程从管道中读出数据,因此称为管道通信。为协调双方,管道机制必须提供三方面协调能力。

  1. 互斥:一个进程对 pipe 读写时,其它进程必须等待
  2. 同步:写满一定数量(如 4KB)后写进程睡眠,读走后再唤醒;读空 pipe 时也睡眠等待
  3. 确定对方是否存在,只有对方存在才能进行通信

远程过程调用(RPC)的执行步骤分两段:客户端发起调用、服务器端执行并返回结果。

客户端先调用本地客户存根,存根打包消息后由本地客户进程发往远程服务器;服务器进程收到消息后转给对应服务器存根,由存根拆包并调用实际过程。

本地客户进程将消息发送到远程服务器进程

本地调用者以一般方式调用远程过程在本地关联的客户存根,传递参数后将控制权转给客户存根。

远程服务器进程接收消息并转入执行

客户存根建立含过程名和调用参数的消息,将控制权转回本地客户进程。

根据远程过程名找到服务器存根并转交消息

服务器存根从消息取出参数,以一般方式调用服务器上关联的过程。

共享存储器系统中,相互通信的进程共享某些数据结构或共享存储区,借此进行通信。按共享对象的不同分为两类。

基于共享数据结构的通信方式较简单,传送数据量小;基于共享存储区的通信方式则在内存中划出一块共享区,进程以高速传送大量数据。

  1. 基于共享数据结构的通信方式
  2. 基于共享存储区的通信方式

客户机-服务器系统中常采用套接字(Socket)和远程过程调用(RPC)实现通信。

套接字基于本地文件系统支持时,通信双方通过对一个特殊文件的读写完成通信;基于网络型时则用于跨主机通信。RPC 是一个通信协议,允许一台主机上的进程调用另一台主机上的进程,对程序员表现为常规过程调用。

基于文件型

套接字关联到一个特殊文件,通常采用非对称方式通信,发送者需提供接收者命名。

基于网络型

用于通过网络连接的系统,支持远程过程调用和远程方法调用。

套接字(Socket)

通信双方通过对特殊文件的读写实现通信,可基于本地文件或网络。

RPC

远程过程调用协议,使本地进程能调用远程主机上的进程,面向对象语境下亦称远程方法调用。

远程过程调用(RPC)的执行过程:客户端调用本地存根,存根将参数打包为消息发送给服务器;服务器存根解包并调用远程过程,完成后将结果打包返回。

  1. 服务器端远程过程运行完毕后将结果返回给服务器存根
  2. 服务器存根将结果打包为消息并转移控制权给远程服务器进程
  3. 远程服务器进程将消息发送回客户端

2.6.2 消息传递通信的实现方式

消息传递系统中所传递的消息必须有一定格式。可采用变长消息格式,即进程发送消息的长度可变;变长消息在处理和存储上开销更大,但方便了用户。

除格式外,还要考虑进程同步方式和通信链路两类问题。发送或接收完成后,进程可继续运行也可阻塞,需由同步机制协调。

进程的同步方式

发送或接收完成后,进程或者继续发送(接收)或者阻塞,需要同步机制协调通信。

通信链路

为在发送与接收进程间通信而建立的链路,可显式建立后再拆除,也可由系统在发送时自动建立。

单向通信链路

只允许发送进程向接收进程发送消息,或反之。

双向通信链路

既允许 A 向 B 发送,也允许 B 同时向 A 发送。

信箱是一种数据结构,逻辑上分为信箱头和信箱体两部分。

信箱头存放信箱的描述信息,如标识符、拥有者、口令、空格数等;信箱体由若干可存放消息的信箱格组成,格数与每格大小在创建时确定。系统为信箱通信提供创建、撤销、发送、接收等原语。

  1. 信箱头:存放信箱标识符、拥有者、口令、空格数等描述信息
  2. 信箱体:由若干信箱格组成,格数和每格大小在创建时确定

信箱通信原语:Send(mailbox,message) 发送消息到指定邮箱;Receive(mailbox,message) 从指定邮箱接收消息。

邮箱可由操作系统或用户进程创建,创建者即拥有者,据此分为私用邮箱、公用邮箱、共享邮箱三类。

系统为邮箱通信提供了若干条原语,用于邮箱的创建、撤销和收发消息。邮箱可由 OS 或用户进程创建,据此分为三类。

  1. 邮箱的创建和撤销
  2. 私用邮箱
  3. 公用邮箱
  4. 共享邮箱
图2-16 双向信箱示意图 进程 A 信箱 进程 B 两边都能投信、取信
图2-16 双向信箱示意图

系统为邮箱通信提供了若干条原语,分别用于。

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

  1. 邮箱的创建和撤销
  2. 私用邮箱
  3. 公用邮箱
  4. 共享邮箱

信箱通信原语

信箱的类型

消息的发送和接收。 Send(mailbox,message); 将一个消息发到指定邮箱 Receive(mailbox,message); 从指定邮箱中接收一个消息

2.6.3 直接消息传递系统实例

消息缓冲队列通信方式中,主要的数据结构是消息缓冲区;进程的 PCB 中也要增加若干通信数据项,用于管理消息队列并实现同步。

PCB 中需设置消息队列队首指针,以及用于互斥的信号量 Mutex 和表示消息资源的信号量 sm,配合消息缓冲队列完成消息的发送与接收。

消息缓冲区

消息缓冲队列通信方式中的主要数据结构,用于暂存待传递的消息。

PCB 通信数据项

包括消息队列队首指针、互斥信号量 Mutex、资源信号量 sm。

接收进程调用接收原语 receive(b),从自己的消息缓冲队列 mq 中摘下第一个消息缓冲区 i。

随后将该缓冲区中的数据复制到以 b 为首址的指定消息接收区,完成一次消息接收。

图2-17 消息缓冲通信 发送者 消息缓冲队列 接收者 消息挂在接收者的缓冲队列上
图2-17 消息缓冲通信

接收进程调用接收原语 receive(b),从自己的消息缓冲队列 mq 中摘下第一个消息缓冲区 i,将数据复制到以 b 为首址的接收区。

做 · 实验

  • 打开工作台 L11,默认进入 los_queue.c,对照 Write/Read 如何阻塞与唤醒。
  • 烧录 18-queue,确认 send/recv 编号一致。
  • 根据「学习目标」列出 3 个关键词,对照原理段落解释给自己听。

打开工作台

进程通信

用控件单步操作;日志区记下每一步发生了什么。

代码导读

本课在工作台编译烧录。先读 user 实验入口,再对照 kernel / HDF 中被调用的函数,不要一上来改链接脚本。

总结与提升

  • 能区分同步机制与消息通信。
  • 能指出队列满/空时任务落在哪个等待结构。
  • 能用自己的话复述学习目标,并指出概念与板上实验的对应。

延伸思考

  • 若引入用户态线程,消息队列应放内核还是用户库?
  • 如果把本节机制拿到 Linux 或完整 OpenHarmony 上,会多出哪些硬件假设?

← 本阶段封面 · 课程列表