进程通信,是指进程之间的信息交换,其所交换的信息量少者是一个状态或数值,多者则是成千上万个字节。进程之间的互斥和同步,由于其所交换的信息量少而被归结为低级通信。信号量机制作为同步工具是卓有成效的,但作为通信工具,则不够理想,主要表现在下述两方面:①效率低,每次只能向缓冲池投放一个产品(消息),消费者每次只能从缓冲区中取得一个消息;②通信对用户不透明。
利用低级通信工具实现进程通信非常不方便,因为共享数据结构的设置、数据的传送、进程的互斥与同步等,都必须由程序员去实现。因此本节主要介绍高级进程通信,是指用户可直接利用操作系统所提供的一组通信命令高效地传送大量数据的一种通信方式。操作系统隐藏了进程通信的实现细节。或者说,通信过程对用户是透明的。这样就大大降低了通信程序编制上的复杂性。
3.5.1 进程的通信方式
高级通信机制可归结为三大类:共享存储器系统、消息传递系统以及管道通信系统。
1.共享存储器系统
在共享存储器系统(shared-memory system)中,相互通信的进程共享某些数据结构或共享存储区,进程之间能够通过这些空间进行通信。据此,又可把它们分成以下两种类型:
(1)基于共享数据结构的通信方式。在这种通信方式中,要求诸进程公用某些数据结构,借以实现诸进程间的信息交换。如在生产者-消费者问题中,就是用有界缓冲区这种数据结构来实现通信的。这里,公用数据结构的设置及对进程间同步的处理,都是程序员的职责。这无疑增加了程序员的负担,而操作系统却只需提供共享存储器。因此,这种通信方式是低效的,只适于传递相对少量的数据。
(2)基于共享存储区的通信方式。为了传输大量数据,在存储器中划出了一块共享存储区,诸进程可通过对共享存储区中数据的读或写来实现通信。这种通信方式属于高级通信。进程在通信前,先向系统申请获得共享存储区中的一个分区,并指定该分区的关键字;若系统已经给其他进程分配了这样的分区,则将该分区的描述符返回给申请者,继之,由申请者把获得的共享存储分区连接到本进程上;此后,便可像读、写普通存储器一样地读、写该公用存储分区。
2.消息传递系统
消息传递系统(message passing system)是当前应用最为广泛的一种进程间的通信机制。在该机制中,进程间的数据交换是以格式化的消息(message)为单位的;在计算机网络中,又把message称为报文。程序员直接利用操作系统提供的一组通信命令(原语),不仅能实现大量数据的传递,而且隐藏了通信的实现细节,使通信过程对用户是透明的,从而大大降低了通信程序编制的复杂性,因而获得了广泛的应用。在当今最为流行的微内核操作系统中,微内核与服务器之间的通信,无一例外地都采用了消息传递机制。又由于它能很好地支持多处理机系统、分布式系统和计算机网络,因此它也成为这些领域最主要的通信工具。消息传递系统的通信方式属于高级通信方式,因其实现方式的不同又进一步分成直接通信方式和间接通信方式两种。
1)直接通信方式
在直接通信方式下,企图发送或接收消息的每个进程必须指出信件发给谁或从谁处接收消息,可用send原语和receive原语来实现进程之间的通信,这两个原语定义如下:
(1)send(P,消息):把一个消息发送给进程P。
(2)receive(Q,消息):从进程Q接收一个消息。
这样,进程P和Q通过执行这两个操作而自动建立了一种连接,并且这种连接仅仅发生在这一对进程之间。消息可以有固定长度或可变长度两种。固定长度便于物理实现,但使程序设计增加困难;而消息长度可变使程序设计变得简单,但使消息传递机制的实现复杂化。
2)间接通信方式
采用间接通信方式时,进程间发送或接收消息通过一个共享的数据结构——信箱来进行,消息可以被理解成信件,每个信箱有一个唯一的标识符。当两个以上的进程有一个共享的信箱时,它们就能进行通信。间接通信方式解除了发送进程和接收进程之间的直接联系,在消息的使用上灵活性较大。一个进程也可以分别与多个进程共享信箱,于是一个进程可以同时和多个进程进行通信。一对一的关系允许在两个进程间建立不受干扰的专用通信链接;多对一的关系对客户机/服务器间的交互非常有用;一个进程给许多别的进程提供服务,这时的信箱又称为一个端口(port),端口通常归接收进程所有并由接收进程创建,当一个进程被撤销时,它的端口也随之被毁灭;一对多的关系适用于一个发送者和多个接收者,它对于在一组进程间广播一条消息的应用程序十分有用。间接通信方式中的“发送”和“接收”原语的形式如下:
(1)send(A,信件):把一封信件(消息)传送到信箱A。
(2)receive(A,信件):从信箱A接收一封信件(消息)。
信箱是存放信件的存储区域,每个信箱可以分成信箱头和信箱体两部分。信箱头指出信箱容量、信件格式、存放信件位置的指针等;信箱体用来存放信件,信箱体分成若干个区,每个区可容纳一封信。
“发送”和“接收”两条原语的功能为:
(1)发送信件。如果指定的信箱未满,则将信件送入信箱中由指针所指示的位置,并释放等待该信箱中信件的等待者;否则,发送信件者被置成等待信箱状态。
(2)接收信件。如果指定信箱中有信,则取出一封信件,并释放等待信箱的等待者;否则,接收信件者被置成等待信箱中信件的状态。
两个原语的算法描述如下,其中,R( )和W( )是让进程入队和出队的两个过程:
type st ruct{
int size; /*信箱大小*/
int count; /*现有信件数*/
message:letter[n]; /*信箱*/
semaphore:S1,S2; /*等信箱和等信件信号量*/
}box;
void send(box B,message M)
{int i;
if(B.count=B.size)
W(B.s1);
i=B.count+1;
B.letter[i]=M;
B.count=i;
R(B.S2)
}
void receive(box B,message x)
{int i;
i f(B.count=0)
W(B.s2);
B.count=B.count-1;
x=B.let ter[1];
i f(B.count≠0)
{
for(int i=1;i<=B.count;i++)
B.let ter[i]=B.letter[i+1];
R(B.S1);
}
}
下面的程序是用消息传递机制解决生产者-消费者问题的一种解法:
int capacity; /*缓冲大小*/(www.xing528.com)
int i;
void producer()
{message pmsg;
while(true)
{
pmsg=produce; /*生产消息*/
receive(mapproduce,pmsg); /*等待空消息*/
bui ld message; /*构造一条消息*/
send(mayconsume,pmsg); /*发送消息*/
}
}
void consumer();
{message cmsg;
while(true)
{
receive(mayconsume,cmsg); /*接收消息*/
ext ract message; /*取消息*/
send(mayproduce,nul l); /*回送空消息*/
consume(csmg); /*消耗消息*/
}
}
void main() /*主程序*/
{creat-mai lbox(mayprocuce); /*创建信箱*/
creat-mai lbox(mayconsume);
for(int i=1;I<=capacity;i++)
send(mayproduce,nul l); /*发送空消息*/
producer();
consumer();
}
这里对该程序做简单说明,假设消息大小相同,程序中共用了capacity条消息,类似于共享内存缓冲的capacity个槽。初始化时,由主控程序负责发capacity条空消息给生产者(也可由消费者发出)。当生产者生产出一个数据后,它接收一条空消息并回送一条填入数据的消息给消费者。通过这种方式,系统中总的消息数保持不变。如果生产者速度比消费者快,则所有空消息取尽,于是生产者阻塞以等待消费者返回一条空消息。如果消费者速度快,则正好相反,所有的消息均为空,等待生产者填充数据发送消息来释放它。
在这种解法中,生产者和消费者均创建了足够容纳capacity条消息的信箱,消费者向生产者信箱发空消息,生产者向消费者信箱发含数据的消息。当使用信箱时,通信机制知道:目标信箱中容纳那些已被发送到的但尚未被目标进程接收的消息。
3.管道通信系统
所谓“管道”,是指用于连接一个读进程和一个写进程以实现它们之间通信的一个共享文件,又名pipe文件。向管道(共享文件)提供输入的发送进程(即写进程),以字符流形式将大量的数据送入管道;而接收管道输出的接收进程(即读进程),则从管道中接收(读)数据。
由于发送进程和接收进程是利用管道进行通信的,故又称管道通信。这种方式首创于UNIX系统,由于它能有效地传送大量数据,因而又被引入许多其他的操作系统中。为了协调双方的通信,管道机制必须提供以下三方面的协调能力:
(1)互斥,即当一个进程正在对pipe执行读/写操作时,其他(另一)进程必须等待。
(2)同步,指当写(输入)进程把一定数量(如4 KB)的数据写入pipe,便去睡眠等待,直到读(输出)进程取走数据后,再把它唤醒。当读进程读一空pipe时,也应睡眠等待,直至写进程将数据写入管道后,才将之唤醒。
(3)确定对方是否存在,只有确定了对方已存在时,才能进行通信。
3.5.2 有关消息传递的若干问题
1.通信链路
为使在发送进程和接收进程之间能进行通信,必须在两者之间建立一条通信链路(communication link)。有两种方式建立通信链路。第一种方式是由发送进程在通信之前用显式的“建立连接”命令(原语)请求系统为之建立一条通信链路;在链路使用完后,也用显式方式拆除链路。这种方式主要用于计算机网络中。第二种方式是发送进程无须明确提出建立链路的请求,只需利用系统提供的发送命令(原语),系统会自动地为之建立一条链路。这种方式主要用于单机系统中。根据通信链路的连接方法,又可把通信链路分为以下两类:
(1)点-点连接通信链路,这时的一条链路只连接两个结点(进程)。
(2)多点连接链路,指用一条链路连接多个(n>2)结点(进程)。而根据通信方式的不同,则又可把链路分成以下两种:
(1)单向通信链路,只允许发送进程向接收进程发送消息,或者相反。
(2)双向链路,既允许由进程A向进程B发送消息,也允许进程B同时向进程A发送消息。
还可根据通信链路容量的不同把链路分成两类:一是无容量通信链路,在这种通信链路上没有缓冲区,因而不能暂存任何消息;二是有容量通信链路,指在通信链路中设置了缓冲区,因而能暂存消息。缓冲区数目越多,通信链路的容量越大。
2.消息的格式
在消息传递系统中所传递的消息,必须具有一定的消息格式。在单机系统环境中,由于发送进程和接收进程处于同一台机器中,有着相同的环境,故其消息格式比较简单;但在计算机网络环境下,不仅源和目标进程所处的环境不同,而且信息的传输距离很远,可能要跨越若干个完全不同的网络,致使所用的消息格式比较复杂。通常可把一个消息分成消息头和消息正文两部分,消息头包括消息在传输时所需的控制信息,如源进程名、目标进程名、消息长度、消息类型、消息编号及发送的日期和时间;而消息正文则是发送进程实际上所发送的数据。
在某些OS中,消息采用比较短的定长消息格式,这便减少了对消息的处理和存储开销。这种方式可用于办公自动化系统中,为用户提供快速的便笺式通信;但这对要发送较长消息的用户是不方便的。在有的OS中,采用变长的消息格式,即进程所发送消息的长度是可变的。系统无论在处理还是在存储变长消息时,都可能会付出更多的开销,但这方便了用户。这两种消息格式各有其优缺点,故在很多系统(包括计算机网络)中,两种格式同时使用。
3.进程同步方式
在进程之间进行通信时,同样需要有进程同步机制,以使诸进程间能协调通信。不论是发送进程还是接收进程,在完成消息的发送或接收后,都存在两种可能性,即进程或者继续发送(接收),或者阻塞。由此可得到以下三种情况:
(1)发送进程和接收进程均阻塞。这种情况主要用于进程之间紧密同步(tight synchronization),发送进程和接收进程之间无缓冲时。这两个进程平时都处于阻塞状态,直到有消息传递时。这种同步方式称为汇合(rendezrous)。
(2)发送进程不阻塞,接收进程阻塞。这是一种应用最广的进程同步方式。平时,发送进程不阻塞,因而它可以尽快地把一个或多个消息发送给多个目标;而接收进程平时则处于阻塞状态,直到发送进程发来消息时才被唤醒。例如,在服务器上通常都设置了多个服务进程,它们分别用于提供不同的服务,如打印服务。平时,这些服务进程都处于阻塞状态,一旦有请求服务的消息到达,系统便唤醒相应的服务进程,去完成用户所要求的服务。处理完后,若无新的服务请求,服务进程又阻塞。
(3)发送进程和接收进程均不阻塞。这也是一种较常见的进程同步形式。平时,发送进程和接收进程都在忙于自己的事情,仅当发生某事件使它无法继续运行时,才把自己阻塞起来等待。例如,在发送进程和接收进程之间联系着一个消息队列时,该消息队列最多能接纳n个消息,这样,发送进程便可以连续地向消息队列中发送消息而不必等待;接收进程也可以连续地从消息队列中取得消息,也不必等待。只有当消息队列中的消息数达到n个时,即消息队列已满,发送进程无法向消息队列中发送消息时才会阻塞;类似地,只有当消息队列中的消息数为0,接收进程已无法从消息队列中取得消息时才会阻塞。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。