进程管理(十)-进程通信
什么是进程通信
通信(communication)意味着在进程间传送数据。
低级通信VS高级通信
- 控制信息的传送。进程间控制信息的交换称为低级通信。
- 大批量数据传送。进程间大批量数据的交换称为高级通信。
进程的通信的四种方式
主从式
特点:
1.主进程可自由地使用从进程的资源或数据
2.从进程的动作受主进程的控制
3.主进程和从进程的关系是固定的
例子:主从式通信系统的典型例子是终端控制进程和终端进程
会话式
会话方式中,通信进程双方可分别称为使用进程和服务进程。其中,使用进程调用服务进程提供的服务,具有如下特点
1.使用进程在使用服务进程所提供的服务之前,必须得到服务进程的许可;
2.服务进程根据使用进程的要求提供服务,但对所提供服务的控制由服务进程自身完成。
3.使用进程和服务进程在通信时有固定连接关系。
消息或邮箱机制
特点:消息或邮箱机制则无论接收进程是否已准备好接收消息,发送进程都将把所要发送的消息送入缓冲区或邮箱。
组成:消息的一般形式为4个部分组成:发送进程名、接收进程名、数据和有关数据的操作。
主要特点:
1.只要存在空缓冲区或邮箱,发送进程就可以发送消息。
2.与会话系统不同,发送进程和接收进程之间无直接连接关系,接收进程可能在收到某个发送进程发来的消息之后,又转去接收另一个发送进程发来的消息。
3. 发送进程和接收进程之间存在缓冲区或邮箱用来存放被传送消息。
共享存储区方式
特点:
1.共享存储区方式不要求数据移动
2.两个需要互相交换信息的进程通过对同一共享数据区(shared memory)的操作来达到互相通信的目的
3.共享数据区是每个互相通信进程的一个组成部分
消息缓冲机制
什么是消息缓冲机制
简而言之,就是利用消息缓冲区进行数据传送
发送进程:发送进程在发送消息前,先在自己的内存空间设置一个发送区,把欲发送的消息填入其中,然后再用发送过程将其发送出去。
接受进程:接收进程则在接收消息之前,在自己的内存空间内设置相应的接收区,然后用接收过程接收消息
需要满足的条件
1.在发送进程把消息写入缓冲区和把缓冲区挂入消息队列时,应禁止其他进程对该缓冲区消息队列的访问。否则,将引起消息队列的混乱。同理,当接收进程正从消息队列中取消息缓冲时,也应禁止其他进程对该队列的访问。
2.当缓冲区中无消息存在时,接收进程不能接收到任何消息。至于发送进程是否可以发送消息,则
由发送进程是否申请到缓冲区决定。
消息缓冲机制的实现
设公用信号量mutex 为控制对缓冲区访问的互斥信号量,其初值为1 。
设SM为接收进程的私用信号量,表示等待接收的消息个数,其初值为0 。
设发送进程调用过程send(m)将消息m 送往缓冲区,接收进程调用过程Receive(m)将消息m从缓冲区读往自己的数据区,则Send(m)和Receive(n)可分别描述为:
Send(m):
begin
向系统申请一个消息缓冲区;
P(mutex);
将发送区消息m送入新申请的消息缓冲区;
把消息缓冲区挂入接收进程的消息队列;
V(mutex);
V(SM);
end
Receive(n):
begin
P(SM);
P(mutex);
摘下消息队列中的消息n ;
将消息n从缓冲区复制到接收区;
释放缓冲区;
V(mutex);
end
邮箱通信
什么是邮箱通信
邮箱通信就是由发送进程申请建立一与接收进程链接的邮箱。
发送进程把消息送往邮箱,接收进程从邮箱中取出消息,从而完成进程间信息交换。
邮箱通信的好处
设置邮箱的最大好处就是发送进程和接收进程之间没有处理时间上的限制。一个邮箱可考虑成发送进程与接收进程之间的大小固定的私有数据结构,它不像缓冲区那样被系统内所有进程共享。
邮箱通信的实现
邮箱由邮箱头和邮箱体组成。其中邮箱头描述邮箱名称、邮箱大小、邮箱方向以及拥有该邮箱的进程名等。
只有一发送进程和一接收进程使用的邮箱,进程间通信应满足如下条件:
1.发送进程发送消息时,邮箱中至少要有一个空格能存放该消息。
2.接收进程接收消息时,邮箱中至少要有一个消息存在。
设发送进程调用过程 deposit(m)将消息发送到邮箱,接收进程调用过程remove(m)将消息m 从邮箱中取出。
为记录邮箱中空格个数和消息个数,信号量fromnum 为发送进程的私用信号量,信号量mesnum为接收进程的私用信号量。fromnum 的初值为信箱的空格数 n,mesnum 的初值为 0。
进程通信实例
和控制台的通信
通用计算机中,除了用户终端之外,还有一台由系统操作员控制的控制台终端。
各用户进程可将消息送到控制台进程,操作员在读到这些消息后做出相应的处理。设控制台终端由键盘和显示器组成,终端和主机之间按全双工模式发送和接收数据,即键盘和数据显示彼此独立。
设键盘控制进程和显示控制进程分别为KCP 和DCP,用户进程和控制台终端的通信由会话控制进程CCP 控制完成
KCP和DCP的动作
首先,当操作员打键时,KCP将对应的数据从键盘送入输入缓冲inbuf,同时也将键入数据送echobuf在显示器上显示。显然,KCP和CCP等之间的通信满足消息机制的条件。另外,除了KCP和CCP的通信之外,KCP实际上还在和键盘发生通信。因此,在描述KCP和CCP时,还必须考虑KCP和键盘的通信。
键盘控制进程KCP
设T-Ready 和T-Busy分别为键盘KP和键盘控制进程KCP 的私用信号量,其初值为0和1。
初始化{清除所有inbuf 和echobuf}
begin
local x;
P(T-Ready);
从键盘数据传输缓冲x中取出字符m记为x.m;
Send(x.m);
将x.m送入echonuf;
V(T-Busy);
end
键盘动作KP
repeat
local x;
P(T-Busy);
把键入字符放入数据传输缓冲x;
V(T-Ready);
until 终端关闭
显示器控制进程DCP
设D-Ready 和D-Busy分别为DP和DCP的私用信号量且初值为0和1 。
初始化 {清除输出缓冲outbuf,echo模式置false}
begin
if outbuf满
then
receive(k) /* CCP k */
P(D-Busy);
把k送入显示器数据缓冲区;
V(D-Ready);
else
echo模式置true;
echobuf 中字符置入显示器数据缓冲区;
fi
显示器动作DP
假定一个消息的长度总是小于outbuf的长度。
repeat
if echo模式
then
打印显示器数据缓冲区中字符
else
P (D-Ready)
打印显示器数据缓冲区中消息
V (D-Busy)
Until 显示器关机
CCP和KCP及DCP的接口
假定1个消息的长度小于outbuf和inbuf的长度。
设过程Read(x)把inbuf 中的所有字符读到用户进程数据区x处,过程Write(y)把用户进程y 处的消息写到outbuf中。
CCP 与用户进程的接口
用户进程向CCP发出的提问消息组成队列RQ, 设相应的互斥信号量rq,初值为1。
CCP所发出的回答消息组成消息接收队列Sqi,互斥信号量sqi,其初值为1。
CCP设置私用信号量question计算用户进程提出问题的数目,信号量question初值为0。
CCP设置私用信号量answeri计算SQi中的消息个数,信号量answeri初值为0。
CCP 的动作
管道pipe
什么是管道
UNIX系统从System Ⅴ开始,提供有名管道和无名管道两种数据通信方式,这里介绍无名管道。
无名管道为建立管道的进程及其子孙提供一条以比特流方式传送消息的通信管道。该管道在逻辑上被看作管道文件,在物理上则由文件系统的高速缓冲区构成,很少启动外设
管道的工作
发送进程利用系统调用write(fd[1], buf, size),把buf中的长度为size字符的消息送入管道入口fd[1]。
接收进程则使用系统调用read(fd[0], buf, size)从管道出口fd[0] 读出size字符的消息置入buf 中。
管道按FIFO(先进先出)方式传送消息,且只能单向传送消息。
利用UNIX提供的系统调用pipe,可建立一条同步通信管道。其格式为: pipe(fd) int fd[2];
其中,fd[1]为写入端,fd[0]为读出端
管道的实例
1.用C语言编写一个程序,建立一个pipe,同时父进程生成一个子进程,子进程向pipe中写入一字符串,父进程从pipe中读出该字符串。
#include 〈stdio.h〉
main()
{
int x, fd[2];
char buf[30], s[30];
pipe(fd);/*创建管道*/
while((x=fork())==-1);/*创建子进程失败时,循环*/
if(x==0)
{
sprintf(buf,″This is an example\n″);
write(fd[1],buf,30);/*把buf中字符写入管道*/
exit(0);
}
else/*父进程返回*/
{
wait(0);
read(fd[0],s,30);/*父进程读管道中字符*/
printf(″%s″,s);
}
}
2.编写一程序,建立一个管道。同时,父进程生成子进程P1,P2,这两个子进程分别向管道中写入各自的字符串,父进程读出它们
#include 〈stdio.h〉
main()
{
int i,r,p1,p2,fd[2];
char buf[50],s[50];
pipe(fd);/*父进程建立管道*/
while((p1=fork())==-1);/*创建子进程P1,失败时循环*/
if(p1==0) /*由子进程P1返回,执行子进程P1*/
{
lockf(fd[1],1,0);/*加锁锁定写入端*/
sprintf(buf,″child process P1 is sending messages!\n″);
printf(″child processP1!\n″);
write(fd[1],buf,50);/*把buf中的50个字符写入管道*/
sleep(5);/*睡眠5秒,让父进程读*/
lockf(fd[1],0,0);/*释放管道写入端*/
exit(0);/*关闭P1*/
}
else/*从父进程返回,执行父进程*/
{
while((p2=fork())==-1);/*创建子进程P2,失败时循环*/
if(p2==0)/*从子进程P2返回,执行P2*/
{
lockf(fd[1],1,0);/*锁定写入端*/
sprintf(buf,″child process P2 is sending messages\n″);
printf(″child process P2 ! \n″);
write(fd[1],buf,50);/*把buf中字符写入管道*/
sleep(5);/*睡眠等待*/
lockf(fd[1],0,0);/*释放管道写入端*/
exit(0);/*关闭P2*/
}
wait(0);
if(r=read(fd[0],s,50)==-1)
printf(″can′t read pipe\n″);
else printf(″%s\n″,s);
wait(0);
if(r=read(fd[0],s,50)==-1)
printf(″can′t read pipe\n″);
else printf(″%s\n″,s);
exit(0);
}
}