2.6 进程通信
目录
参考资料:
信号量机制也算是一种进程通信的方法,但是不能传递数据,并且效率低并且对用户不透明。
OS 提供的高级通信工具:
优点:
- 使用方便
- 高效地传送信息
四大类型:
- 共享存储器系统
- 管道通信系统
- 消息传递系统
- 客户机-服务机系统
共享存储器系统 sms
一个进程不能访问其他进程的地址空间,通过共享存储器系统可以创建一个共享的地址空间,所有的进程都可以访问这个地址空间。
需要自己控制互斥访问。
- 基于共享数据结构的通信方式:传输少量数据,属于低级通信
- 基于共享存储区的通信方式:传输大量数据,属于高级通信
管道通信系统 pipe PP
管道通信系统的本质是在内存中开辟一块缓冲区,缓冲区与管道相关联,进程对管道的操作实际上就是对缓冲区的操作。
管道通信系统的特点:
- 半双工的通信方式(可以创建 2 个管道实现全双工)
匿名管道
shell
# EG Linux
command1 | command2
ps -ef | grep nginx
指令 |
的本质就是创建了一个匿名管道,将前一个指令的输出作为后一个指令的输入。
匿名管道的特点:
- 只能在具有公共祖先的进程之间使用。
- 通过管道符
|
创建的管道是匿名管道,用完了就会被自动销毁
C
// Linux 底层使用pipe()函数来创建匿名管道
// 创建成功返回0,失败返回-1
// fd[0] 是管道的读端,fd[1] 是管道的写端
int pipe (int fd[2]);
命名管道/有名管道 FIFO
命名管道的特点:
- 可以在不具有公共祖先的进程之间使用,通过访问文件的方式来使用
- 用完了不会自动销毁,需要手动销毁
- 有名管道是一种特殊的文件,可以通过
mkfifo
命令来创建
消息传递系统 MPS
直接消息传递
OS 提供了发送和接受消息的原语 send receive 来实现直接消息传递。
原语
- 对称寻址方式
需要知道对方的进程标识符
c
send(receiver, message); // 发送消息给一个进程
receive(sender, message); // 接收一个进程的消息
- 非对称寻址方式
c
send(receiver, message); // 发送消息给一个进程
receiver(id, message); // 接收一个进程的消息
消息格式
本地的话消息格式简单一点
进程间同步方式
通信链路
- 单向链路:
- 双向链路:
消息缓冲队列通信机制
消息缓冲队列通信机制是一种间接消息传递机制。
需要发送的进程将消息放入消息缓冲队列中,接收的进程从消息缓冲队列中取出消息。
这个消息缓存记录在 PCB 中,每个 PCB 都有一个消息缓冲队列。
数据结构:
ts
/**
* 消息缓冲区
*/
type struct message_buffer {
int sender; //发送者进程标识符
int size; //消息长度
char text; //消息正文
struct message buffer next; //指向下一个消息缓冲区的指针
}
/**
* PCB 进程控制块
*/
type struct processcontrol_block {
...
struct message_buffer mq; //消息队列队首指针
semaphore mutex; //消息队列互斥信号量
semaphore sm; //消息队列资源信号量
...
} PCB;
发送原语
c
void send(receiver,a) { //1-发送区:receiver为接收进程标识符,a为发送区首址;
getbuf(a.size,i); //2-缓冲区:根据a.size申请缓冲区;
copy(i.sender,a.sender); //3-缓冲区复制操作:将发送区a中的信息复制到消息缓冲区i中;
i.size=a.size;
copy(i.text,a.text);
i.next=0;
getid(PCBset,receiver.j); //4-获取接收者标识:获得接收进程内部的标识符;
P(j.mutex);
insert(&j.mq,i); //5-消息插入:将消息缓冲区插入接受进程的消息队列;
V(j.mutex);
V(j.sm); //6-唤醒数据消费:资源信号量+1
}
接收原语
c
void receive(b) {
j = internal name; //j为接收进程内部的标识符;
P(j.sm); //如果资源为空,此时将阻塞
P(j.mutex); //使用同步原语操作j.mq
remove(j.mq, i); //将消息队列中第一个消息移出;
V(j.mutex);
copy(b.sender, i.sender); //缓冲区->接收区,将消息缓冲区i中的信息复制到接收区b:
b.size =i.size;
copy(b.text, i.text);
releasebuf(i); //释放消息缓冲区;
}
间接消息传递
发送和接受消息,都通过共享中间实体(信箱)来实现。
信箱通信
一个信箱包括一个信箱头和一个信箱体,信箱体包含多个信箱单元,每个信箱单元都可以存放一个消息。
信箱存放在内存中。
- 信箱头:记录信箱的状态
- 信箱的标识符
- 信箱的拥有者
- 信箱口令
- 信箱空格数
- 信箱体:有多个各自存放消息
操作系统提供创建,销毁,发送和接收等原语来实现信箱通信
ts
// 发送消息
send(mailbox, message);
// 接收消息
receive(mailbox, message);
信箱类型:
- 私有信箱:只能被一个进程拥有,其他进程只允许往这个信箱里发送消息,进程结束时消失
- 公共邮箱:所有进程都可以读取或者发送消息到邮箱,邮箱一般在系统运行期间一直存在
- 共享邮箱:创建时需要指定共享的对象,拥有者和共享者都有权收取自己的消息
进程间的关系:
- 一对一
- 多对一:一个服务多个客户
- 多对多:多个服务多个客户
- 一对多:一个发送进程多个接收进程,类似广播
客户机-服务机系统
可以在不同的计算机上运行的进程之间进行通信。
socket 套接字
一个套接字包含:
- 通信目的地地址
- 通信使用的端口号
- 通信网络的传输层协议
- 进程所在的网络地址
- API
- 等
- 基于文件型
类似管道,套接字关联到一个文件,进程通过对文件的操作来实现通信。
- 基于网络型
分为服务端和客户端,服务端绑定一个端口,客户端通过端口来访问服务端。
客户端的端口号是动态分配的。
远程过程调用 RPC & 远程方法调用
远程调用允许一个进程调用另一个在不同地址空间的进程的过程,就像调用本地过程一样。