进程间通信
特点:
- 进程间传递大量数据
- 采用 OS 提供的高级通信工具,OS 隐藏了实现该进程通信的细节,向用户提供一组用于实现高级通信的原语。
目前,高级进程间通信机制可归结为四类:
- 共享存储器系统(Shared-Memory System)
- 管道(pipe)通信系统
- 消息传递系统(Message passing system)
- 客户机-服务器系统(Client-Server system)
1. 共享存储器系统(Shared-Memory System)
在共享存储器系统中,相互通信的进程共享某些数据结构或共享存储区,进程之间能通过这些空间进行通信。
- 基于共享数据结构的通信方式:适合传递少量数据,效率低
- 基于共享存储区的通信方式:在内存中划出一块共享存储区域,诸进程先向系统申请其中一块区域,并将其附加到自己的地址空间中,便可在此读写,数据的形式和位置甚至访问控制都是由进程负责
2. 管道(pipe)通信系统
管道是用于连接一个读进程和一个写进程以实现它们之间通信的一个共享文件。向管道提供输入的发送进程以字符流形式将大量的数据送入管道,而接受管道输出的接收进程则从管道中接收数据。管道机制需要确定发送进程和接收进程双方都存在时才能通信,只允许一个进程对 pipe 文件进行读写操作,发送进程写如一定数据后进入等待,并唤醒接收进程,反之亦然。
3. 消息传递系统(Message passing system)
在该机制中,进程不必借助任何共享存储区域或数据结构,而是以格式化的消息为单位,将通信的数据封装在消息中,并利用操作系统提供的一组通信命令(原语)在进程间传递消息。
按实现方式不同,可分为两类:
- 直接通信方式:发送指令进程利用 OS 所提供的发送原语,直接把消息发送给目标进程
- 间接通信方式:发送和接收进程通过共享中间实体(即邮箱)的方式进行消息的发送和接收
3.1. 直接消息传递系统
发送指令进程利用 OS 所提供的发送原语,直接把消息发送给目标进程。
直接通信原语
- 对称寻址方式:发送和接收进程都以显式方式提供对方的标识符,例如
send(receiver, message);
和receive(sender, message);
,修改名称需要修改所有引用该名称的原语(不方便) - 非对称寻址方式:一收多发的进程通信,发送进程原语需要命名接收进程,接收进程原语不需要命名发送进程,而是填写表示源进程的参数。例如,
send(receiver, message);
和receiver(id, message);
消息格式
单机系统环境可用较短的定长消息格式,而需要发送长消息的用户则需要采用变长的消息格式。
进程的同步方式
按完成消息的发送和接收后的阻塞情况分为
- 发送和接收进程均阻塞:主要用于紧密同步,无缓冲
- 发送进程不阻塞而接收进程阻塞
- 发送和接收进程均不阻塞
通信链路
为使发送和接收进程能进行通信,需要在两者之间建立一条通信链路。有两种建立方式
- 发送进程在通信前用显式的「建立连接」命令向系统申请一条通信链路,主要用在计算机网络中
- 发送进程无需申请链路,只需系统提供的发送原语,系统会自动建立一条通信链路,主要用于单机系统
按通信方向可将通信链路分为三种。
实例:消息缓冲队列通信机制
// 消息缓冲区
typedef struct message_buffer{
int sender; // 发送者进程标识符
int size; // 消息长度
char *text; // 消息
struct message_buffer *next;
}
// 在进程 PCB 中设置消息缓冲队列和对其的操作
typedef struct processcontrol_block{
struct message_buffer *mq; // 消息队列首指针
semaphore mutex; // 消息队列互斥信号
semaphore sm; // 消息队列资源信号量
}
// 发送原语
void send(receiver, a){ // a 为发送区首地址
getbuf(a.size,i);
i.sender=a.sender;
i.size=a.size;
copy(i.text,a.text);
i.next=0;
getid(PCBset,receiver.j); // 获得接收进程内部的标识符 j
wait(j.mutex);
insert(&j.mq,i);
signal(j.mutex);
signal(j.sm);
}
// 接收原语
void receive(b){
j=internal name;
wait(j.sm);
wait(j.mutex);
remove(j.mq,i); // 将消息队列中的第一个信息移出
signal(j.mutex);
b.sender=i.sender;
b.size=i.size;
copy(b.text,i.text);
releasebuf(i);
}
3.2. 信箱通信
发送和接收进程通过共享中间实体(即邮箱,一个建立在随机存储器的公共缓冲区上的数据结构)的方式进行消息的发送和接收,每个信箱有唯一标识符,邮箱内想消息只允许目标用户读取。
信箱结构
- 信箱头:存放信箱标识符、信箱拥有者、信箱口令和信箱空格数等
- 信箱体:若干个可存放消息的信箱格
信箱通信原语
- 信箱的创建和撤销
- 消息的发送和接收:
send(mailbox, message);
、receive(mail, message);
信箱类型
- 私用信箱:用户进程可为自己建立一个信箱,仅拥有者有权读取
- 公共信箱:有操作系统建立,供系统中所有核准进程使用
- 共享信箱:由进程创建,信箱拥有者和共享者可读取
4. 客户机-服务器系统(Client-Server system)
客户机-服务器系统是网络环境的主流,其主要实现方法有三种:
- 套接字
- 远程过程调用
- 远程方法调用
4.1. 套接字
一个套接字就是一个通信标识类型的数据结构,包含通信目的地址、通信使用的端口号、网络通信的传输协议、进程所在的网络地址和针对客户或服务程序提供的不同系统调用(或 API 函数),是进程通信和网络通信的基本构件。套接字为客户-服务器模型而设计,通常包括两类:
- 基于文件型:通信进程都运行在同一台机器环境中,套接字基于本地文件系统支持,一个套接字关联到一个特殊文件,通信双方通过对该文件读写实现通信。
- 基于网络型:通常采用非对称方式通信,即发送者需要提供接收者命名。发送进程发出连接请求,随机申请一个套接字,主机为之分配一个端口与套接字绑定;接收进程拥有全局公认的套接字和指定端口(例如 http 服务器监听端口:80),并通过监听端口等待用户请求。
4.2. 远程过程调用和远程方法调用
远程过程调用(Remove Procedure Cal)是一个通信协议,允许运行一台主机系统上的进程调用另一台主机系统上的进程,而对程序员表现为常规调用。该机制通过网络守护进程,即本地客户进程和远程服务器进程,进行网络间消息传递,一般这俩进程处于阻塞状态等待消息。
为实现远程调用的透明,引入了存根(client stubborn)。在本地,每个独立运行的进程进程都拥有一个客户存根供本地进程调用;每个远程进程所在的服务器,其所对应的实际可执行进程也存在一个服务器存根与其关联。实现步骤如下:
- 本地过程调用客户存根
- 客户存根执行后,将控制权转移给本地客户进程
- 本地客户进程将消息发送给远程服务器进程
- 远程服务器进程执行,把消息转个服务器存根
- 服务器存根执行,拆开消息(逆向是打包),调用服务器上的关联进程
- 在服务器客户端的远程进程运行完毕后,将结果原路返回
在采用面向对象编程的软件中,远程过程调用也可称远程方法调用。
5. ChangeLog
2018.08.20 初稿