4.1 进程间的相互作用
4.1.1 进程间的联系
资源共享关系
相互合作关系
临界资源应互斥访问。
临界区:不论是硬件临界资源,还是软件临界资源,多个进程必须互斥地对它们进行访问。
把在每个进程中访问临界资源的那段代码称为临界资源区。
显然,若能保证诸进程互斥地进入自己的临界区,便可实现它们对临界资源的互斥访问。
为此,每个进程在进入临界区之前,应先对欲访问的临界资源进行检查,看其是否正在被访问。
如果没被访问,则进入临界区,且设置正访问标志;如果正被访问,则不进入临界区。
同步机制应遵循的标准:
1、空闲让进
2、忙则等待
3、有限等待 对要求访问临界资源的进程,应保证该进程能在有效的时间进入自己的临界区,以免陷入“死等”状态
4、让权等待 当进程不能进入自己的临界区时,应立即释放处理机,以免进程陷入“忙等”状态
—————————————————————————————————————————————————————————————————
4.1.2 利用软件方法解决进程互斥问题
全局共享变量,适用于两个进程,非重点,用于说明遇到的情况。现在很少采用。
算法1:
设置一个公用整形变量turn,用于指示被允许进入临界区的编号,即若turn=1,表示允许p1进入临界区。
main()
{
cobegin{
p1;
p2;
}
}
对p1:
while (1)
{
while (turn != 1) no-op;
critical section;
turn = 2;
}
该算法可以确保每次只允许一个进程进入临界区。但是强制两个进程轮流进入临界区,资源利用不充分。
如p1退出临界区后将turn置2,如果此时p2不进入临界区,且p1又想再次访问临界区,违背“空闲让进”原则。
算法2:
算法1的问题在于:它采取了强制的方法让p1和p2轮流访问临界资源,完全不考虑它们的实际需要。
算法2的思想是对两个线程置两个标志位flag1和flag2。若flag1=1,表示p1正在执行;若flag2=1,表示p2正在执行。
int flag1=0;
int flag2=0;
对p1:
while (1)
{
while (flag2 != 0) no-op;
flag1=1;
critical section;
flag1=0;
}
如果两个进程在开始时几乎同时进入临界区,因而同时发现对方的访问标志是0,于是两个进程都先后进入临界区,此时违背了“忙则等待”原则。
算法3:
使要进入临界区的进程先设置其要求进入的标志,然后再去查看其它进程的标志。
对p1:
while (1)
{
flag1=1;
while (flag2 != 0) no-op;
critical section;
flag1=0;
}
此种算法导致死锁,违背了“有限等待”、“空闲让进”准则。
算法4:
组合了算法1和算法3的概念。
p1将flag1置1代表希望进入临界区,并将turn置2代表允许p2进入临界区;判断flag2 && turn=2为真,等待,否则,进入。
对p1:
while (1)
{
flag1=1;
turn=2;
while (flag2 && turn==2) no-op;
critical section;
flag1=0;
}
此算法即保证了“空闲让进”,又保证了“忙则等待”。
上述4种算法均为忙式等待,不满足“让权等待”。
—————————————————————————————————————————————————————————————————
4.1.3 利用硬件方法解决进程互斥问题
安全利用软件方法来解决诸进程互斥进入临界区的问题有一定难度且有很大局限性,因而现在已很少采用。
现在许多计算机已提供了一些特殊的硬件指令,这些指令允许对一个字中的内容进行检测和修正,或交换两个字的内容。
1、利用Test-and-Set指令实现互斥
int TS(static int lock)
{
int TS=lock;
lock=1;
return(TS);
}
lock=0表示资源空闲。
为了实现诸进程对临界资源的互斥访问,可为每个临界资源设置一个全局变量lock并赋初值0,表示资源空闲。
用TS指令记录变量lock的状态,并将1赋予lock,这等效于关闭了临界区。
while (1)
{
while(TS(lock)) no-op;
critical section;
lock=0;
}
2、利用Swap指令实现进程互斥
Swap指令称为交换指令。在微机中该指令又称为XCHG指令,用于交换两个字的内容。
void Swap(static int a,b)
{
int temp;
temp=a;
a=b;
b=temp;
}
可为临界资源设置一个全局变量lock。其初值为0,在每个进程中再利用一个局部变量key。
while (1)
{
key=1;
do{
swap(lock, key);
} while (key);
critical section;
lock=0;
}
利用硬件指令能有效实现进程互斥,但却不能满足“让权等待”准则,造成处理机时间的浪费,而且也很难将其用于解决较复杂的进程同步问题。
—————————————————————————————————————————————————————————————————
4.1.4 信号量机制
应用广泛:单处理机系统、多处理机系统、计算机网络
信号量的类型
·信号量分为: 互斥信号量 和 资源信号量
·互斥信号量用于申请或释放资源的使用权,常初始化为1
·资源信号量用于申请或归还资源,可以初始化为大于1的正整数,表示系统中某类资源的可用个数。
·wait操作用于申请资源(或使用权),进程执行wait原语时,可能会阻塞自己。
·signal操作用于释放资源(或归还资源使用权),进程执行signal原语时,有责任唤醒一个阻塞进程。
信号量的意义
·互斥信号量:申请/释放使用权,常被初始化为1
·资源信号量:申请归还资源,资源信号量可以初始化为一个正整数,表示系统中某类资源的可用个数。 S.count的意义为
>S.count >= 0 表示还可执行wait(S)而不会阻塞的进程数(可用资源)
>S.count < 0 表示S.queue队列中阻塞进程的个数(被阻塞进程数)
S.count的取值范围
·当仅有两个并发进程共享临界资源时,互斥信号量仅能取值 -1 0 1
其中S.count=1,表示无进程进入临界区
S.count=0,表示已有一个进程进入临界区
S.count=-1,表示已有一个进程正在等待进入临界区
·当用S来实现n个进程互斥时,S.count的取值范围为 1 -(n-1)
·操作系统内核以系统调用形式提供wait 和 signal原语,应用程序通过系统调用实现进程间的互斥。
P(S)=wait(S)
V(S)=signal(S)
·工程实践证明,利用信号量方法实现进程互斥是高效的,一直被广泛采用。
1、记录型信号量机制
typedef struct{
int value; //整型值,代表资源数目
list of process *L; //链表,链接所有等待该信号量代表资源的进程
}semaphore;
两个标准原子操作:wait(s)和signal(s) 这两个操作长期以来被称为P、V操作。
原子性:执行过程不可中断,即当一个进程在修改某信号量时,没有其他进程可同时对该信号量进行修改。
void wait(static semaphore s)
{
s.value--;
if (s.value < 0) block(s.L);
}
void signal(static semaphore s)
{
s.value++;
if (s.value <= 0) wakeup(s.L);
}
s.value的初值表示系统中某类资源的数目,因为又称为资源信号量。
每次wait操作意味着请求一个单位的资源,描述为s.value--;当s.value<0时,表示资源已分配完毕,用block原语进行自我阻塞,放弃处理机并插入到信号量链表s.L中。遵循“让权等待”原则。此时,s.value的绝对值数代表在该信号量链表中。
每次signal操作,表示执行进程释放一个单位资源,故s.value++;加1后若s.value<=0,则表示在该信号量链表中仍有等待该资源的进程被阻塞,故还应调用wakeup原语,唤醒进程访问临界资源。
信号量实现互斥
semaphore mutex=1;
void procedure1()
{
while(1)
{
wait(mutex);
critical section;
signal(mutex);
}
}
void procedure2()
{
while(1)
{
wait(mutex);
critical section;
signal(mutex);
}
}
main()
{
cobegin{
procedure1();
procedure2();
}
}
注意:wait(mutex)和signal(mutex)必须成对出现。
缺少wait(mutex)将导致系统混乱,不能保证对临界资源的互斥访问;
缺少signal(mutex)将会使临界资源永远不被释放。
还可用信号量来描述程序或语句之间的前驱关系。
信号量初值为0
可利用信号量,按照语句的前趋关系,写出一个可并发执行的程序。
main(){
semaphore a=b=c=d=e=f=g=0;
cobegin{
{T1; signal(a); signal(b);}
{wait(a); T2; signal(c); signal(d)}
{wait(b); T3; signal(e)}
{wait(c); T(4); signal(f)}
{wait(d); T(5); signal(g)}
{wait(e); wait(f); wait(g); T6}
}
2、信号量集机制
(1)AND型信号量集机制
上述互斥针对进程之间要共享一个临界资源而言的。
在有些应用场合,一个进程需要先获得两个或更多的共享资源后方能执行任务。
process P:
wait(Amutex);
wait(Bmutex);
process Q:
wait(Bmutex);
wait(Amutex);
此种情况可能引起死锁。
AND同步机制的基本思想是,将所需的所有临界资源一次性全部分配给进程,该进程用完后一次性全部释放。
对若干临界资源的分配采取原子操作方式,要么全部分配,要么一个都不分配。
void Swait(s1, s2, ..., sn)
{
if(s1>=1 && s2>=1 && ... && sn>=1)
for(i=1; i<=n; ++i)
si.value--;
else
Place the process in the awaiting queue associated with the si found with si<1,and set the program count of this count of this process to the beinning of Swait operation.
}
void Ssignal(s1, s2, ..., sn)
{
for(i=1; i<=n; ++i)
{
si.value++;
Remove all the process waiting in the queue associated with si into the ready queue;
}
(2)一般信号量集
上述信号量机制,只能对信号量进行加一减一操作。
扩充:
可以加n减n;
在小于某个值时,不分配。
void Swait(s1, t1, d1, s2, t2, d2, ..., sn, tn, dn)
{
if(s1>=t1 && s2>=t2 && ... && sn>=tn)
for(i=1; i<=n; ++i)
si.value-di;
else
Place the process in the awaiting queue associated with the si found with si<ti,and set the program count of this count of this process to the beinning of Swait operation.
}
void Ssignal(s1, t1, d1, s2, t2, d2, ..., sn, tn, dn)
{
for(i=1; i<=n; ++i)
{
si.value+=di;
Remove all the process waiting in the queue associated with si into the ready queue;
}
几种一般“信号量集”的特殊情况:
Swait(s,d,d)。信号量集中只有一个信号量,允许每次申请d个资源,当资源数少于d时,不予分配。
Swait(s,1,1)。退化为一般信号量(s>1)或互斥信号量(s==1)。
Swait(s,1,0)。开关型信号量。
———————————————————————————————————————————————————
4.1.5 管程机制
信号量机制对于每个要访问临界资源的进程都必须自备同步操作wait(s)和signal(s),这使得大量的同步操作分散在各个进程中。这不仅给系统的管理带来麻烦,还会因为同步操作的使用不当而导致死锁。
把所有进程对某一种临界资源的同步操作都集中起来,构成一个所谓的“秘书”进程。
凡要访问该临界资源的进程,都需要先报告“秘书”,有秘书来实现诸进程的同步。
把并发进程间的同步操作分别集中于相应的管程中。
系统中的各种硬件资源和软件资源,均可用数据结构加以抽象描述,即用少量信息和对该资源所执行的操作来表征该资源,而忽略了它们的内部结构和实现细节。
当共享资源用共享数据结构表示时,资源管理程序可用对该数据结构进行操作的一组过程来表示,如资源的请求和释放过程request和release。把这样一组相关的数据结构和过程一并称为管程。
一个管程定义了一个数据结构和能为并发进程所执行(在该数据结构上)的一组操作,这组操作能同步进程和改变管程中的数据。
管程分3部分:
1、局部于管程的共享变量说明;
2、对该数据结构进行操作的一组过程;
3、对局部于管程数据的数据设置初值的语句。
语法描述:
monitor monitor-name{
variable declarations
{ initialization code}
entry p1(...){...}
entry p2(...){...}
... ...
entry pn(...){...}
}
局部于管程的数据,仅能被局部于管程的过程所访问,局部于管程的过程也仅能访问管程内的数据结构。
管程相当于围墙,每次只允许一个进程进入管程,从而实现进程互斥。
在利用管程实现同步时,必须设置两个同步操作原语wait和signal。
当某进程通过管程请求临界资源而未能满足时,管程便调用wait原语使该进程等待,并将其排在等待队列上。
仅当另一进程访问完并释放之后,管程调用signal原语唤醒等待队列中的队首进程。
通常,等待的原因可能有多个,为了进行区别,引入条件变量condition。 管程中对每个条件变量都需予以说明,其形式为:“condition x,y;”,该变量应置于wait和signal之前,可表示为x.wait和x.signal。
x.signal操作的作用是重新启动一个被阻塞的进程,但如果没有进程被阻塞,则x.signal操作不产生任何后果,这与信号量机制中的signal操作不同。
——————————————————————————————————————————————————
4.1.6 经典进程同步问题
1、生产者-消费者问题(producer-consumer)
有一群生产者进程在生产产品,并将此产品提供给消费者进程去消费。
并发进行,设置一个n个缓冲区的缓冲池。
不允许消费者到空缓冲区去取产品,也不允许生产者进程向一个已有产品,尚未取走的缓冲区投放产品。
in指示下一个可投放产品的缓冲区,生产者生产一个产品后,输入指针加1,即in=(in+1)mod n;
out指示下一个可获取产品的缓冲区,消费者消费一个产品后,输出指针加1,即out=(out+1)mod n。
(in+1)mod n == out 时表示缓冲池满;
in == out 时表示缓冲池空。
还引入了一个整型变量counter,初始值为0。生产者投放产品后,counter+1;消费者消费产品后,counter-1.
(1)利用记录型信号量解决生产者-消费者问题
互斥信号量mutex实现诸进程对缓冲池的互斥使用
资源信号量empty表示空缓冲区的数量
资源信号量full表示满缓冲区的数量
semaphore mutex=1, empty=n, full=0
item buffer[n];
int in=out=0;
void producer()
{
while(1)
{
produce an item in nextp; //nextp代表产品
wait(empty);
wait(mutex);
buffer[in]=nextp;
in=(in+1)mod n;
signal(mutex);
signal(full);
}
}
void consumer()
{
while(1)
{
wait(full);
wait(mutex);
nextc=buffer[out];
out=(out+1)mod n;
signal(mutex);
signal(empty);
consume the item in nextc;
}
}
main()
{
cobegin{
producer();
consumer();
}
}
注意以下几点:
1、在每个程序中实现互斥的wait(mutex)和signal(mutex)应该成对出现;
2、对资源信号量empty和full的wait和signal操作,同样需要成对出现,但它们分别处于不同的进程;
3、在每个进程中的wait操作不能颠倒。应先执行对资源信号量的wait操作,然后再执行对互斥信号量的wait操作,否则引起死锁。
生产者放产品和消费者取产品无需互斥进行。
semaphore mutex1=1, mutex2=1, empty=n, full=0
item buffer[n];
int in=out=0;
void producer()
{
while(1)
{
produce an item in nextp; //nextp代表产品
wait(empty);
wait(mutex1);
buffer[in]=nextp;
in=(in+1)mod n;
signal(mutex1);
signal(full);
}
}
void consumer()
{
while(1)
{
wait(full);
wait(mutex2);
nextc=buffer[out];
out=(out+1)mod n;
signal(mutex2);
signal(empty);
consume the item in nextc;
}
}
main()
{
cobegin{
producer();
consumer();
}
}
(2)利用AND信号量解决生产者-消费者问题
semaphore mutex1=1, mutex2=1, empty=n, full=0
item buffer[n];
int in=out=0;
void producer()
{
while(1)
{
produce an item in nextp; //nextp代表产品
Swait(empty, mutex1);
wait(mutex1);
buffer[in]=nextp;
in=(in+1)mod n;
Ssignal(mutex1, full);
}
}
void consumer()
{
while(1)
{
Swait(full, mutex2);
nextc=buffer[out];
out=(out+1)mod n;
Ssignal(mutex2,empty);
consume the item in nextc;
}
}
main()
{
cobegin{
producer();
consumer();
}
}
(3)利用管程解决生产者-消费者问题
monitor producer-consumer
{
int in,out,count;
item buffer[n];
condition notfull, notempty;
{in=out=0; count=0;}
entry put(item)
{
if(count>=n) notfull.wait;
buffer[in]=nextp;
in=(in+1)mod n;
count++;
notempty.signal;
}
entry put(item)
{
if(count<=0) notempty.wait;
nextc=buffer[out];
out=(out+1)mode n;
count--;
notfull.signal;
}
}
void producer()
{
while(1)
{
produce an item in nextp;
producer-consumer.put(item);
}
}
void consumer()
{
while(1)
{
producer-consumer.get(item);
consume the item in nextc;
}
}
main()
{
cobegin{
producer();
consumer();
}
}
2、读者写者问题
对于数据对象
允许多个读者进程同时访问;只允许一个写者进程互斥访问。
即要么有多个读者,要么只有一个写者。
对于读者写者不同的优先权,可以有两种变形:
第一类读者写者问题,读者优先——读者在读,后续来的读者可直接进入。可能导致写者饿死。
第二类读者写者问题,写者优先——写者欲读,所有后续读者均等待。可能导致读者饿死。
(1)利用记录型信号量解决第一类读者-写者问题
semaphore rmutex=mutex=1;
int readcount=0;
void reader(int i)
{
while(1)
{
wait(rmutex)
if(readcount==0)
wait(mutex);
readcount++;
signal(rmutex);
perform read operation;
wait(rmutex);
readcount--;
if(readcount==0)
signal(mutex);
signal(rmutex);
}
}
void writer(int j)
{
while(1)
{
wait(mutex);
perform write operation;
signal(mutex);
}
}
mian()
{
cobegin{
reader(1);
...
reader(n);
writer(1);
...
writer(n);
}
(2)利用记录型信号量集机制解决第一类读者-写者问题
增加一条限制,最多允许RN个读者同时读。
#define RN
semaphore L=RN, mx=1;
void reader(int i)
{
while(1)
{
Swait(L,1,1)
Swait(mx,1,0); //起开关作用,只要无写者进入(mx==1),读者就可进入。
perform read operation;
signal(L,1);
}
}
void writer(int j)
{
while(1)
{
Swait(mx,1,1,L,RN,0; //仅当无写者进程(mx==1),又无读者进程(L=RN)时,写者才进入
perform write operation;
Signal(mx,1);
}
}
mian()
{
cobegin{
reader(1);
...
reader(n);
writer(1);
...
writer(n);
}
(3)利用管程方法解决第二类读者-写者问题
monnitor reader-writer{
int rc, wc; //记录读进程和写进程数
condition R,W;
{ rc=wc=0}
entry start_read
{
if wc>0 R.wait;
rc++;
R.signal;
}
entry end_read
{
rc--;
if rc=0 W.signal;
}
entry start_write
{
wc++;
if( rc>0 || wc>1) W.wait;
}
entry end_write
{
wc--;
if(wc>0) W.signal;
else R.signal;
}
}
Reader(int i)
{
while(1)
{
reader_writer.start_read;
reading;
reader_writer.end_read;
}
}
Writer(int j)
{
while(1)
{
reader_writer.start_write;
reading;
reader_writer.end_write;
}
}
mian()
{
cobegin{
reader(1);
...
reader(n);
writer(1);
...
writer(n);
}
3、哲学家进餐问题
5位哲学家,交替思考和进餐。
一张圆桌,桌上有5支筷子。
平时思考,饥饿时同时拿起左右两只筷子即可进餐。
进餐完毕,放下筷子继续思考。
semophore chopstick[5]={1,1,1,1,1};
wodi process(int i)
{
while(1)
{
wait(chopstick[i]);
wait(chopstick[(i+1)mod 5]);
eat;
signal(chopstick[i]);
signal(chopstick[(i+1)mod 5]);
think;
}
}
上述过程可能引起死锁。
几种解决方案:
1、最多允许4位哲学家同时进餐;
2、仅当两支筷子都可用时,才允许拿起筷子进餐;
3、规定奇数哲学家先拿左边筷子,偶数哲学家先拿右边筷子。最后总有一个哲学家能获得两支筷子而进餐。
利用AND信号量机制解决哲学家进餐问题
semaphore chopstick[5]={1,1,1,1,1}
void process(int i)
{
while(1)
{
Swait(chopstick[(i+1)mod 5], chopstick[i]);
eat;
Ssignal(chopstic[(i+1)mod 5], chopstick[i]);
think;
}
}
main()
{
cobegin{
process(0);
process(1);
process(2);
process(3);
process(4);
process(5);
}
}
—————————————————————————————————————————————————————————
4.2 进程通信
进程通信是指进程之间的信息交换。少则是一个状态或数值,多则是成千上万个字节。
进程的互斥和同步时低级通信。
高级通信是指用户可直接利用操作系统所提供的一组通信命令高效地传送大量数据。
在高级通信方式中,操作系统隐藏了进程通信的实现细节,或者说通信过程对用户是透明的。
4.2.1 进程通信的类型
三大类:共享存储器系统、消息传递系统、管道通信系统
1、共享存储器系统
相互通信的进程共享某些数据结构或共享存储区。
(1)基于共享数据结构的通信方式。程序员负责公用数据结构设置和同步处理,低效,适合少量数据。
(2)基于共享存储区的通信方式。高级通信,进程在通信前,向系统申请共享存储区中的一个分区,并指定该分区的关键字;若系统已经给其它进程分配了这样的分区,则将该分区的描述符返回给申请者。接着申请者把获得的共享存储分区连接到本进程上。此后,便可像读、写普通存储器一样地读写公用存储区。
2、消息传递系统
进程间的数据交换以消息为单位。
直接利用系统提供的通信命令(原语)来实现通信。
操作系统隐藏了通信的实现细节,大大简化了通信程序编制的复杂性,因而应用广泛。
高级通信方式
(1)直接通信方式:
发送进程直接将消息发送给接受进程并将它挂在接受进程的消息缓冲队列上,接受进程从消息缓冲队列中取得消息。
(2)间接通信方式:
发送进程将消息发送到某种中间实体中,接受进程从中取得消息。这种中间实体称为信箱。这种通信方式称为信箱通信方式。
3、管道通信
管道是用于连接一个读进程和一个写进程以实现它们之间通信的共享文件,又称为pipe文件。
写进程以字符流的形式将大量的数据写入管道,读进程从管道读取信息。
管道通信机制必须提供以下3方面的协调能力。
互斥、同步、判断对方是否存在。
4.2.2 直接通信和间接通信
1、直接通信方式
直接通信方式是指发送进程利用操作系统所提供的发送命令直接把消息发送给目标进程。
此时,要求发送进程和接收进程都以显示的方式提供对方的标识符。
通常系统提供下述两条通信原语:
send(receiver, message);
receive(sender, message);
利用直接通信原语来解决生产者-消费者问题:
void producer()
{
while(1)
{
produce an item in nextp;
send(consumer, nextp);
}
}
void consumer()
{
while(1)
{
receive(producer,nextc);
consume the item in nextc;
}
}
main(){
producer();
consumer();
}
2、间接通信方式
进程之间的通信需要通过作为某种共享数据结构的实体,该实体用来暂存发送进程发给目标进程的消息;接收进程从该实体中取出对方发送给自己的消息。通常把这种中间实体称为信箱。
在逻辑上,信箱由信箱头和包括若干信格的信箱体所组成,每个信箱必须有自己的唯一标识符。
利用信箱进行通信,用户可以不必写出接受进程标识符就可以向不知名的进程发送消息,且信息可以安全的保存在信箱中,允许目标用户随时读取。
这种通信方式被广泛地用于多机系统和计算机网络中。
系统为信箱通信提供了若干条原语,用于信箱的创建、撤销和消息的发送、接受。
信箱可由操作系统创建,也可由用户进程创建。
分三类:私有信箱、公用信箱、共享信箱
发送进程和接收进程的四种关系:
一对一
多对一:客户/服务器交互
一对多:广播
多对多
4.2.3 消息缓冲队列通信机制
send原语
receive原语
消息缓冲区:
struct message_buffer{
sender; //发送者进程标识符
size; //消息长度
text; //消息正文
next; //指向下一个缓冲区的指针
};
PCB中有关通信的数据项
struct processcontrol_block{
mq; //消息队列首指针
mutex; //消息队列互斥信号量
sm; //消息队列资源信号量
}
发送原语:
发送进程在利用发送原语发送消息之前应首先在自己的内存空间设置一发送区a,把待发送的消息正文、发送进程标识符、消息长度等信息传入其中,然后调用发送原语,把消息发送给接收进程。
发送原语首先根据发送区a中所设置的消息长度a.size来申请一个缓冲区i,接着把发送区a的信息复制到消息缓冲区i中。
为了能将i挂在接收进程的消息队列mq,上,应先获得接收进程的内部标识符j,然后将i挂在j.mq上。
由于该队列属于临界资源,故在执行insert操作的前后要分别执行wait和signal操作。
void send(receiver, a)
{
getbuf(a.size, i); //根据a.size申请缓冲区
i.sender=a.sender; //将a中的信息复制到缓冲区i中
i.size=a.size;
i.text=a.text;
i.next=0;
getid(PCB set, receiver, j); //获得接收进程内部标识符j
wait(j.mutex);
insert(j.mq,i); //将消息缓冲区插入消息队列
singal(j.mutex);
signal(j.sm);
}
接收原语:
接收原语进程调用接收原语receive(b)从自己的消息缓冲队列mq中摘下第一个消息缓冲区i,并将其中的数据复制到以b为首地址的指定消息接收区内。
void receive(b)
{
j=inernal.name; //j为接收进程的内部标识符
wait(j.sm);
wait(j.mmutex);
remove(j.mq, i); //将消息队列中第一个消息移出
signal(j.mutex);
b.sender=i.sender; //把消息缓冲区i中的信息复制到接收区b
b.size=i.size;
b.next=i.next;
}
————————————————————————————————————————————————————————
Massage Passing
·Enforce mutual exclusion
·Exchange information
send(destination, message)
receive(source, message)
Synchronization
·Sender and receiver may or may not be blocked( waiting for message)
·Blocking send, blocking receive
---Both sender and receiver are blocked until message is delivered.
---Called a rendezvous(紧密同步,汇合)
·Non-blocking send, blocking receive
---Sender continues processing such as sending messages as quickly as possible.
---Receive is blocked until the requested message arrives.
·Non-blocking send, non-blocking receive
---Neither part is required to wait.
Addressing 寻址
·Direct addressing
---Send primitive includes a specific identifier of the destination process.
---Receive primitive could know ahead of time which process a message is expected.
---Receive primitive could use source parameter to return a value when the receive operation has been performed.
·Indirect addressing
---Messages are sent to a shared data structure consisting of queues.
---Queues are called mailboxes.
---One process sends a message to the mailbox and the other process picks up the message from the mailbox.
Message Format
Header Body >Message Contents
>Message Type
>Destination ID
>Source ID
>Message Length
>Control Information
Mutual Exclusion
·若采用Non-blocking send, blocking receive
·多个进程共享mailbox mutex
若进程申请进入临界区,首先申请从mutex邮箱中接受一条消息。
若邮箱空,则进程阻塞;若进程收到邮箱中的消息,则进入临界区,执行完毕退出,并将该消息放回邮箱mutex。
该消息as a token(令牌)在进程间传递。
Massage Passing: Producer/Consumer Problem
·解决有限buffer Problem/Consumer Problem
·设两个邮箱:
---May_consume:Producer存放数据,供Consumer取走(即buffer数据区)
---May_produce:存放空消息的buffer空间