11:49
3.5 进程通信
并发进程之间的交往本质上是互相交换信息 。 有些情况下进程之间交换的信息量很少,例如仅仅交换某个状态信息 。
有些情况下进程之间交换大批数据,例如传送一批信息或整个文件 。 进程之间互相交换信息的工作称之为进程通信
IPC(InterProcess Communication)。
11:49
进程间通信的方式包括
通过软中断提供的信号 ( signal) 通信机制 ;
使用信号量及其原语操作 ( PV,读写锁,管程或其他操作 ) 控制的共享存储区 (shared memory)通信机制 ;
通过管道 (pipeline)提供的共享文件 (shared file)通信机制 ;
使用信箱和发信 /收信原语的消息传递 ( message
passing)通信机制 。
其中前两种通信方式属于低级通信机制,仅适用于集中式操作系统 。 消息传递机制属于高级通信机制,共享文件通信机制是消息传递机制的变种,这两种通信机制,既适用于集中式操作系统,又适用于分布式操作系统 。
11:49
3.5.1 信号通信机制
信号机制又称软中断,是一种简单的通信机制,通过发送一个指定信号来通知进程某个异常事件发生。
信号不但能从内核发给一个进程,也能由一个进程发给另一个进程。
11:49
Unix系统信号多达几十种 (不超过 32种 ),主要分成以下几类:
与进程终止相关的信号 SIGCLD,SIGHUP,SIGKILL、
SIGCHLD等,如进程结束,进程杀死子进程,;
与 进 程 例 外 事 件 相 关 的 信 号 SIGBUS,SIGSEGV、
SIGPWR,SIGFPE等,如进程执行特权指令,写只读区,地址越界,总线超时,硬件故障 ;
与进程执行系统调用相关的信号 SIGPIPE,SIGSYS、
SIGILL等,如进程执行非法系统调用,管道存取错 ;
与进程终端交互相关的信号 SIGINT,SIGQUT等,如进程挂断终端,用户按 delete键或 break健 。
用 户 进 程 发 信 号 SIGTERM,SIGALRM,SIGUSR1、
SIGUS2等,如进程向另一进程发一个信号,要求报警 ;
跟踪进程执行的信号 SIGTRAP等 。
11:49
3.5.2 共享文件通信机制 `
管道 (pipeline)是连接读写进程的一个特殊文件,允许进程按先进先出方式传送数据,也能使进程同步执行操作 。 如下图所示,发送进程以字符流形式把大量数据送入管道,接收进程从管道中接收数据,所以,也叫管道通信 。
11:49
管道的实质是一个共享文件,因此管道通信基本上可以借助于文件系统原有的机制实现,包括 ( 管道 ) 文件的创建,
打开,关闭和读写 。 但是,写入进程和读出进程之间的相互协调单靠文件系统机制是解决不了的 。 读写进程相互协调,
必须做到以下三点:
11:49
进程对通信机构的使用应该是互斥的,一个进程正在使用某个管道写入或读出数据时,另一个进程就必须等待 。 这一点是进程在读写管道之前,通过测试文件 i节点的特征位来保证的 ;
发送者和接收者双方必须能够知道对方是否存在,如果对方已经不存在,就没有必要再发送信息 。 这时会发出
SIGPIPE信号通知进程 ;
由于管道长度有限,发送信息和接收信息之间一定要实现正确的同步关系 。 管道文件最多只能提供 5120字节的缓冲,管道的长度对 write和 read操作会有影响 。 如果执行一次写操作,且管道有足够空间,那么,write把数据写入管道后立即返回 ;如果这次操作会引起管道溢出,则本次 write操作必须暂停,直到其他进程从管道中读出数据,使管道有空间为止,这叫 write阻塞 。
解决此问题的办法是:把数据进行切分,每次最多 5120字节,写完后该进程睡眠,直到读进程把管道中的数据取走,并判别有进程等待时应唤醒他,以便继续写下一批数据 。 反之,当读进程读空管道时,要出现读阻塞,读进程应睡眠,直到写进程唤醒他 。
11:49
共享文件
11:49
UNIX中,管道的定义如下:
int pipe(files);
int files[2];
11:49
父子进程通过管道传送信息的一个例子:
#include<stdio.h>
#define MSGSIZE 16
char *msg1=”hello,world#1”;
char *msg2=”hello,world#2”;
char *msg3=”hello,world#3”;
11:49
main( )
{
char inbuf[MSGSIZE];
int p[2],j,pid;
/*open pipe*/
if(pipe<0) {
perror(“pipe call”);
exit(1);
}
if((pid=fork( )<0) {
perror(“fork call”);
exit(2);
}
11:49
/*if parent,then close read file descriptor and write down pipe*/
if(pid>0 {
close(p[0]);
write(p[1],msg1,MSGSIZE);
write(p[1],msg2,MSGSIZE);
write(p[1],msg3,MSGSIZE);
wait((int*)0);
}
/*if child,then close write file descriptor and read from pipe*/
if (pid==0) {
close(p[1]);
for(j=0;j<3;j++)
read(p[0],inbuf,MSGSIZE);
printf(“%s\n,inbuf”);
}
}
exit(0);
}
11:49
3.5.3 共享存储区通信机制
Process A
Process B
Shared memory
kernel一
11:49
与共享存储有关的系统调用有四个:
shmget(key,size,permflags)
shmat(shm-id,daddr,shmflags)
Shmdt(memptr)
Shmctl(shm-id,command,&shm-stat)
11:49
3.5.4消息传递
消息传递 ( message passing)
send原语,向一个给定的目标发送一个消息
receive原语,则从一个给定的源接受一条消息
直接通信 ( 消息缓冲区 ) 方式
间接通信 ( 信箱 ) 方式
11:49
直接通信
企图发送或接收消息的每个进程必须指出信件发给谁或从谁那里接收消息
原语 send( P,消息 ),把一个消息发送给进程 P
原语 receive( Q,消息 ),从进程 Q接收一个消息
11:49
间接通信
进程间发送或接收消息通过一个信箱来进行,消息可以被理解成信件
原语 send( A,信件 ),把一封信件 ( 消息 ) 传送到信箱 A
原语 receive( A,信件 ),从信箱 A接收一封信件 ( 消息 )
11:49
间接通信的实现
信箱是存放信件的存储区域,每个信箱可以分成信箱特征和信箱体两部分 。 信箱特征指出信箱容量,信件格式,指针等;信箱体用来存放信件
发送信件:如果指定的信箱未满,则将信件送入信箱中由指针所指示的位置,并释放等待该信箱中信件的等待者;否则发送信件者被置成等待信箱状态
接收信件:如果指定信箱中有信,则取出一封信件,并释放等待信箱的等待者,否则接收信件者被置成等待信箱中信件的状态
11:49
type box=record
size:integer; /*信箱大小
count:integer; /*现有信件数
letter:array[1..n] of message; /*信箱
S1,S2:semaphore; /*等信箱和等信件信号量
end
11:49
procedure send(varB:box,M:message)
var I:integer;
begin
if B.count=B.size then W(B.s1);
i:=B.count+1;
B.letter[i]:=M;
B.coumt:=I;
R(B.S2)
end;{send}
11:49
procedure receive(varB:box,x:message)
var i:integer;
begin
if B.count=0 then W(B.s2);
B.count:=B.count-1;
x:=B.letter[1];
if B.count not=0 then for i=1 to b.count do
B.letter[i]:=B.letter[i+1];
R(B.S1);
end;{receive}
11:49
用消息传递机制解决生产者 -消费者问题的程序。
var capacity:integer; /*缓冲大小
i:integer;
procedure producer;
var pmsg:message;
begin
while true do
begin
receive (mapproduce,pmsg); /*等待空消息
pmsg:=produce; /*生产消息
send(mayconsume,pmsg); /*发送消息
end
end;
11:49
procedure consumer;
var cmsg:message;
begin
while true do
begin
receive (mayconsume,cmsg); /*接收消息
consume(csmg); /*消耗消息
send(mayproduce,null); /*发送空消息
end
end;
begin /*主程序
creat-mailbox(mayprocuce); /*创建信箱
creat-mailbox(mayconsume);
for i=1 to capacity do send (mayproduce,null); /*
发送空消息
11:49
3.5.5 有关消息传递实现的若干问题
信箱容量问题
多进程与信箱相连的信件接收问题
信箱的所有权问题
信件的格式问题
通信进程并行性问题
11:49
消息缓冲通信涉及的数据结构有:
sender,发送消息的进程名或标识符
size,发送的消息长度
text,发送的消息正文
next-ptr,指向下一个消息缓冲区的指针
在进程的 PCB中涉及通信的数据结构:
mptr,消息队列队首指针
mutex,消息队列互斥信号量,初值为 1
sm,表示接收进程消息队列上消息的个数,
初值为 0,是控制收发进程同步的信号量
11:49
发送原语和接收原语的实现如下:
发送原语 send,申请一个消息缓冲区,
把发送区内容复制到这个缓冲区中;找到接收进程的 PCB,执行互斥操作 P(mutex) ; 把缓冲区挂到接收进程消息队列的尾部,执行
V(sm),即消息数加 1;执行 V(mutex)。
接收原语 receive,执行 V(sm) 查看有否信件;执行互斥操作 P(mutex),从消息队列中摘下第一个消息,执行 V(mutex) ; 把消息缓冲区内容复制到接收区,释放消息缓冲区 。
11:49
消息缓冲通信接收进程名,Q
信件长,5c
正文,ABCDE
Send(发送区首址 )
发送区消息队列首指针
mutex
sm
发送进程名,P
信件长,5
正文,ABCDE
后继信件缓冲指针
0
进程 Q的 PCB
发送进程名,P
信件长,5
正文,ABCDE
receive(接收区首址 )
接收区进程 P 进程 Q
11:49
Unix的消息传递机制
消息缓冲池和消息缓冲区 (msgbuf),前者包含消息缓冲池大小和首地址 ;后者除存放消息正文外,还有消息类型字段 。
消息头结构和消息头表,消息头表是由消息头结构组成的数组,个数为 100。 消息头结构包含消息类型,
消息正文长度,消息缓冲区指针和消息队列中下一个消息头结构的链指针 。
消息队列头结构和消息队列头表,由于可有多个消息队列,于是对应每个消息队列都有一个消息队列头结构,消息队列头表是由消息队列头结构组成的数组 。
消息队列头结构包括:指向队列中第一个消息的头指针,指向队列中最后一个消息的尾指针,队列中消息个数,队列中消息数据的总字节数,队列允许的消息数据最大字节数,最近一次发送 /接收消息进程标识和时间 。
11:49
消息队列头结构消息队列头表消息头结构消息缓冲区池消息缓冲区
11:49
并行的通信进程的程序应如下编制:
cobegin
procedure P
begin
,..
send(Q,message);
,..
wait(Q,result);
,..
end;
procedure Q
begin
,..
receive(P,message);
,..
answer(P,result);
,..
end;
11:49
管道和套接字
管道 ( pipeline) 是 Unix和 C的传统通信方式
套接字 ( socket) 起源于 Unix BSD版本,目前已经被 Unix和 Windows 操 作系 统 广泛 采 用,并支持
TCP/IP协议,即支持本机的进程间通信,也支持网络级的进程间通信
管道和套接字都是基于信箱的消息传递方式的一种变体,它们与传统的信箱方式等价,区别在于没有预先设定消息的边界 。 换言之,如果一个进程发送
10条 100字节的消息,而另一个进程接收 1000个字节,那么接收者将一次获得 10条消息