进程通信
1.进程通信的类型
高级通信机制主要分为三大类:共享存储器系统、消息传递系统、管道通信系统。
其中,消息传递系统是应用最广泛的机制,也是以下要讨论的主要内容。
(1)共享存储器系统
相互通信的进程共享某些数据结构或者共享存储区,进程之间能够通过这些空间进行通信:
-
基于共享数据结构的通信方式
这种通信方式中,要求诸进程公用某些数据结构,以实现进程间的信息交换。
如生产者-消费者问题中,就是用有界缓冲区这种数据库结构来实现通信。
由于OS只提供共享存储器,程序员需要设置公用数据结构,还需要处理进程间的同步,负担较大,因此这种通信方式是低效的,仅适用于传递相对少的数据。
-
基于共享存储区的通信方式
这种方法属于高级通信。
为了传输大量数据,在存储器中划出了一大块共享存储区,各进程可以通过对共享存储区中数据的读或写来实现通信。
进程在通信前,先向系统申请获得共享存储区中的一个分区,并指定该分区的关键字;若系统已经给其他进程分配了这样的分区,则将分区的描述符返回给申请者。
申请者把获得的共享存储区连接到本进程上。这时就可以像读写普通存储器一样读写共享存储区了。
(2)消息传递系统
是当前应用最为广泛的一种进程间的通信机制。
- 这种机制中,进程间的数据交换以格式化的消息(message,计网中叫报文)为单位。
- 程序员利用OS提供的一组通信命令——原语,实现大量的数据传递,这隐藏了通信的实现细节,大大简化了通信编程的复杂性。
特别值得一提的是,在当今最为流行的微内核操作系统中,微内核与服务器之间的通信,无一例外地都采用了消息传递机制。又由于它能很好地支持多处理机系统、分布式系统和计算机网络,因此它也成为这些领域最主要的通信工具。消息传递系统的通信方式属于高级通信方式。又因其实现方式的不同而进一步分成直接通信方式和间接通信方式两种。
(3)管道通信
管道是指用于连接一个读进程和一个写进程以实现它们之间通信的一个共享文件——pipe文件。
- 向管道提供输入的发送进程,以字符流形式将大量数据送入管道
- 接收进程从管道中接收数据。
这种方式首创于 UNIX 系统,由于它能有效地传送大量数据,因而又被引入到许多其它的操作系统中。
为了协调双方通信,管道机制必须提供以下三方面的协调能力:
-
互斥
当一个进程正在对pipe读/写时,其他进程必须等待
-
同步
当写进程把一定数量的数据写入pipe,便去睡眠等待;直到读进程取走数据后,再把它唤醒。
当读进程读到一个空pipe时,也睡眠等待;直到写进程将数据写入管道后,再把它唤醒。
-
确定对方是否存在
只有确定了对方已存在,才能进行通信。
2.消息传递通信的实现方法
进程通信时,源进程可以直接或间接将消息传送给目标进程,由此可以将进程通信分为直接通信和间接通信两种通信方式。
(1)直接通信方式
发送进程利用OS提供的发送命令,直接把消息发送给目标进程。
此时,要求发送进程和接收进程都以显式方式提供对方的标识符。
通常,系统提供以下两条原语:
Send(Receiver, message);
Receive(Sender, message);
(2)间接通信方式
信箱:
间接通信方式指进程之间的通信需要通过作为共享数据结构的实体(通常把这种中间实体称为信箱)。
该实体用来暂存发送进程发送给目标进程的消息;接收进程则从该实体中取出对方发送给自己的消息。
消息在信箱中安全保存,只允许核准的目标用户随时读取,因此可以用来实现实时或者非实时通信。
原语:
系统为信箱通信提供了若干条原语,分别用于信箱的创建、撤消和消息的发送、接收等:
-
信箱的创建和撤销
进程可利用信箱创建原语来建立一个新信箱。创建者进程应给出信箱名字、信箱属性(公用、私用或共享);对于共享信箱,还应给出共享者的名字。当进程不再需要读信箱时,可用信箱撤消原语将之撤消。
-
消息的发送和接收。当进程之间要利用信箱进行通信时,必须使用共享信箱,并利
用系统提供的下述通信原语进行通信:Send(mailbox, message)
Receive(mailbox, message)
信箱类别:
信箱可以由系统创建,也可以由用户创建,创建者是信箱的拥有者。
信箱可以分为以下三类:
-
私用信箱
- 由进程创建,并作为该进程的一部分。
-
信箱的拥有者有权从信箱中读取消息,其他用户则只能将自己构成的消息发送到该信箱中。
- 这种私用信箱可采用单向通信链路的信箱来实现。
当拥有该信箱的进程结束时,信箱也随之消失。
-
共享信箱
- 由进程创建,在创建时或创建后指明它是可共享的,同时须指出共享进程(用户)的名字。
- 信箱的拥有者和共享者都有权从信箱中取走发送给自己的消息。
-
公用信箱
- 由操作系统创建,并提供给系统中的所有核准进程使用。
- 核准进程既可把消息发送到该信箱中,也可从信箱中读取发送给自己的消息。
- 公用信箱应采用双向通信链路的信箱来实现。
- 通常,公用信箱在系统运行期间始终存在。
发送进程和接收进程之间的关系:
利用信箱通信时,在发送进程和接收进程之间存在以下四种关系:
-
一对一关系
这时可为发送进程和接收进程建立一条两者专用的通信链路,使两者
之间的交互不受其他进程的干扰。 -
多对一关系
即多个客户进程和一个服务器进程交互。
允许提供服务的进程与多个用户进程之间进行交互,也称为客户/服
务器交互(client/server interaction)。 -
一对多关系
发送进程可以广播消息。
允许一个发送进程与多个接收进程进行交互,使发送进程可用广播方
式向接收者(多个)发送消息。 -
多对多关系
允许建立一个公用信箱,让多个进程都能向信箱中投递消息;也可从
信箱中取走属于自己的消息。
3.消息传递系统实现中的主要问题
消息传递系统的三个主要问题是:通信链路、消息格式、进程同步方式
(1)通信链路
为使在发送进程和接收进程之间能进行通信,必须在两者之间建立一条通信链路。
建立链路的两种方式
-
显式:
发送进程在通信之前用显示的建立连接命令请求系统为之建立一条通信链路,在链路使用完后显示释放。(主要用于计网中)
-
非显式:
发送进程无明确提出建立通信链路的请求,只需用系统提供的发送命令(原语),系统会自动为其建立一条链路。(主要用于单机系统)
链路连接的两种方式
-
点对点
一条链路只连接两个节点(这里的节点是进程)
-
多点连接
一条链路连接多个节点
链路通信的两种方式
-
单向链路
只允许发送进程向接收进程发送消息。
-
双向链路
允许两个进程互相发送消息。
两种不同的链路容量
-
无容量链路
在通信链路上没有缓冲区,不能暂存任何消息
-
有容量链路
在链路中设置了缓冲区,能暂存消息。缓冲区数目越多,链路容量越大。
(2)消息格式
在消息传递系统中所传递的消息,必须具有一定的消息格式。
单机系统环境 & 计网环境
-
单机系统环境:
由于发送进程和接收进程处于同一台机器中,有着相同的环境,故其消息格式比较简单;
-
计网环境:
不仅源和目标进程所处的环境不同,而且信息的传输距离很远,可能要跨越若干个完全不同的网络,致使所用的消息格式比较复杂。
消息头 & 消息正文
通常,可把一个消息分成消息头和消息正文两部分。
-
消息头:
包括消息在传输时所需的控制信息,如源进程名、目标进程名、消息长度、消息类型、消息编号及发送的日期和时间
消息正文:
是发送进程实际上所发送的数据。
定长消息格式 & 变长消息格式
-
定长消息格式
在某些OS中,消息采用比较短的定长消息格式,这便减少了对消息的处理和存储开销。
这种方式可用于办公自动化系统中,为用户提供快速的便笺式通信;但这对要发送较长消息的用户是不方便的。 -
变长消息格式
在有的OS中,采用变长的消息格式,即进程所发送消息的长度是可变的。
系统无论在处理还是在存储变长消息时,都可能会付出更多的开销,但这方便了用 户。
这两种消息格式各有其优缺点,故在很多系统(包括计算机网络)中,是同时都用的。
(3)进程同步方式
在进程之间进行通信时,同样需要有进程同步机制,以使诸进程间能协调通信。
不论是发送进程,还是接收进程,在完成消息的发送或接收后,都存在两种可能性——进程继续发送(接收),或者阻塞。
由此,我们可得到以下三种情况:
-
发送进程阻塞,接收进程阻塞
这种情况主要用于进程之间紧密同步(tightsynchronization),发送进程和接收进程之间无缓冲时。这两个进程平时都处于阻塞状态,直到有消息传递时。
这种同步方式称为汇合(rendezrous)。
-
发送进程不阻塞,接收进程阻塞
是应用最广的进程同步方式。
平时,发送进程不阻塞,因而它可以尽快地把一个或多个消息发送给多个目标
而接收进程平时则处于阻塞状态,直到发送进程发来消息时才被唤醒
例如,在服务器上通常都设置了多个服务进程,它们分别用于提供不同的服务,如打印服务。平时,这些服务进程都处于阻塞状态,一旦有请求服务的消息到达时,系统便唤醒相应的服务进程,去完成用户所要求的服务。处理完后,若无新的服务请求,服务进程又阻塞。
-
发送进程和接收进程均不阻塞
也是一种较常见的进程同步方式。
平时,发送进程和接收进程都在忙于自己的事情,仅当发生某事件使它无法继续运行时,才把自己阻塞起来等待。
例如,在发送进程和接收进程之间联系着一个消息队列时,该消息队列最多能 接纳n个消息,这样,发送进程便可以连续地向消息队列中发送消息而不必等待;接收进 程也可以连续地从消息队列中取得消息,也不必等待。
只有当消息队列中的消息数已达到n个时,即消息队列已满,发送进程无法向消息队列中发送消息时才会阻塞;类似地,只有 当消息队列中的消息数为 0,接收进程已无法从消息队列中取得消息时才会阻塞。
4.消息缓冲队列通信机制
消息缓冲队列通信机制首先由美国的Hansan 提出,并在 RC 4000 系统上实现,后来被广泛应用于本地进程之间的通信中。
在这种通信机制中,发送进程利用Send
原语将消息直接发送给接收进程;接收进程则利用Receive
原语接收消息。
(1)消息缓冲队列通信机制中的数据结构
-
消息缓冲区
是主要的数据结构。可以用伪代码描述如下:
//类型message_buffer是一个记录型信号量 type message_buffer = record sender; //发送者标识符 size; //消息长度 text; //消息正文 next; //指向下一个消息缓冲区的指针 end
-
PCB中有关通信的数据项
PCB = Process Control Block = 进程控制块
除了需要为进程设置消息缓冲队列外,还应在进程的PCB中增加消息队列队首指针,用于对消息队列进行操作,以及用于实现同步的互斥信号量mutex和资源信号量 S。
伪代码描述如下:
//process_control也是一个记录型信号量 type process_control = record mq; //message_queue,消息队列首指针 mutex; //消息队列的互斥信号量 S; //资源信号量 end
(2)发送与接收原语
发送原语
设发送进程为a,接收进程为b。
发送进程在利用发送原语发送消息之前,应先在自己的内存空间设置一发送区a。
把待发送的消息正文、发送进程标识符、消息长度等信息填入其中,然后调用发送原语,把消息发送给目标(接收)进程。
发送原语根据发送区a中所设置的消息长度 a.size 来申请一缓冲区buffer,接着把发送区a 中的信息复制到缓冲区i中。
为了能将buffer挂在接收进程的消息队列mq上,应先获得接收进程的内部标识符recv_ID,然后将发送端缓冲区buffer挂在 recv_ID.mq 上。
由于队列recv_ID.mq属于临界资源,故在执行insert操作的前后,都要执行
wait
和signal
操作。
发送原语伪代码描述如下:
procedure send(receiver, a)
begin
getbuf(a.size, send_buffer); //设置一大小为a.size的缓冲区buffer
buffer.sender = a.sender;//将a中存储的发送者标识符复制到和缓冲区buffer
buffer.size = a.size; //记录缓冲区buffer的大小
buffer.text = a.text; //复制消息正文到缓冲区buffer
buffer.next = 0; //指向下一个消息缓冲区的指针设为空
getid(PCB_set, receiver.recv_ID); //从PCB集中获得接收进程的内部标识符
//等待访问缓冲队列所需的互斥信号量
wait(recv_ID.mutex);
insert(recv_ID.mq, buffer); //将缓冲区挂到接收端消息队列
//释放访问缓冲队列所需的互斥信号
signal(recv_ID.mutex);
//S表示消息队列中等待接收端取走的缓冲数
//通知接收方来取
signal(recv_ID.S);
end
接收原语
procedure receive(b)
begin
//等待访问缓冲队列所需的互斥信号量
Swait(recv_ID.mutex);
//将该缓冲从接收端消息队列移除
remove(recv_ID.mq, buffer);
signal(recv_ID.mutex);
b.sender = buffer.sender; //取走缓冲区里存放的发送者信息
b.size = buffer.size; //取走大小信息
b.text = buffer. //取走消息正文
end