【操作系统】同步互斥机制(⼆):管程与进程间通信机制(IPC)
1 管程
1.1 管程的定义
管程(Monitor)是关于共享资源的数据结构及在其上操作的⼀组过程组成。进程只能通过调⽤管程中的过程来间接的访问管程中的数据结构。
1.2 管程需要解决的两个基本问题
1.2.1 互斥
管程是互斥进⼊的,有⼀个进程调⽤管程时,其他进程将不能再调⽤管程,这么设计主要是为了保证数据完整性。管程的互斥是由编译器保证的。
1.2.2 同步
管程中设置条件变量及等待/唤醒操作以解决同步问题。当⼀个进程或线程在条件变量上等待时应先释放管程的使⽤权,也可以通过发送信号将等待在条件变量上的进程或线程唤醒。
1.3 应⽤管程遇到的问题
我们已经知道,进程可以将等待在条件变量上的其他进程唤醒,假如P进程进⼊了管程并唤醒了Q进程,如果P和Q进程同时对管程中的数据操作,那么就会破坏管程中数据的完整性,此时应该如何避免这个问题呢?有三种解决⽅案:
1. P进程等待Q(Hoare管程)
2. Q进程等待P进程继续执⾏(MESA管程)
3. 规定唤醒操作是管程中最后⼀个可执⾏操作。就是说当任何⼀个进程在管程中唤醒了别的进程,那么该进程必须退出管程。
2 Hoare管程
2.1 Hoare管程的核⼼思想
Hoare管程的可以⽤以下⽰意图说明:
如上图所⽰,当某个进程P从⼊⼝等待队列进⼊到Hoare管程中,对资源进⾏各种操作(此时其他所有管程只能在管程外等待),当执⾏某⼀过程n时发现条件cn不⾜,⽆法继续执⾏,此时进程P通过wait操作等在条件变量cn上,此时进程P放弃管程的互斥权使⽤,门打开让其它进程进⼊。假设Q进程进⼊,在进⾏若⼲操作之后,条件变量cn满⾜了,此时Q进程通过signal操作唤醒进程P,⽽根据Hoare管程的语
义,应该由P进程执⾏,⽽Q进程等待。Hoare管程为这类因唤醒其他进程⽽进⼊等待状态的所有进程在管程内开辟了⼀个专门的紧急等待队列。
以上是对管程的⼯作流程做了个详细的介绍,接着对管程补充两点说明:
1. 管程是互斥进⼊的,所以当⼀个进程试图进⼊⼀个已经被占⽤的管程时,应当在管程的⼊⼝等待。为此,管程的⼊⼝处设置了⼀个⼊
⼝等待队列。
2. 如果进程P唤醒了进程Q,则进程P等待进程Q执⾏;⽽进程Q⼜唤醒了进程R,则进程Q等待进程R执⾏;并且进程P、Q进⼊紧急等待
队列,以此类推。所以管程内部可能出现多个等待进程,但任意时刻最多只能有⼀个进程对管程进⾏操作。
紧急等待队列是在管程内部开辟的⼀个进程等待队列,其优先级⾼于⼊⼝等待队列。
2.2 Hoare管程条件变量的实现
条件变量实际上是在管程内部说明和使⽤的⼀种特殊类型的变量。对于条件变量c可以执⾏wait和signal操作。
表2-1 条件变量的实现机制
操作名实现机制
wait(c)如果紧急等待队列⾮空,则唤醒第⼀个等待者;否则释放管程的互斥权,执⾏此操作的进程进⼊c链末尾
signal(c)如果c链为空,则相当于空操作,执⾏此操作的进程继续执⾏;否则唤醒第⼀个等待者,执⾏此操作的进程进⼊紧急等待队列的末尾2.3 Hoare管程的应⽤
本⼩节主要针对利⽤管程解决⽣产者、消费者问题,逻辑和利⽤信号量的PV操作是⼀样的,具体逻辑如程序清单2-1的伪代码。
程序清单2-1 Hoare管程对于⽣产者消费者问题的解决
monitor ProducerConsumer{
/
* 定义两个条件变量,full表⽰缓冲区满了的时候⽣产者停⽌⽣产,empty表⽰缓冲区空了的时候消费者停⽌消费 */
condition full,empty;
/* ⾮空缓冲区的数量 */
integer count;
procedure insert(item :integer);
begin
/* 当缓冲区满了时,⽣产者进程中⽌执⾏,进⼊条件变量full */
if(count==N) then wait(full);
insert_item(item);count++;
/* 当缓冲区数据量为1时,唤醒等待在条件变量empty上的消费者进程 */
it(count==1) then signal(empty);
end;
function remove:integer;
begin
/* 当缓冲区空了时,消费者进程中⽌执⾏,进⼊条件变量empty */
if(count==0) then wait(empty);
remove=remove_item;count--;
/* 当缓冲区数据量为N-1时,唤醒等待在条件变量full上的⽣产者进程 */
if(count==N-1) then signal(full);
end;
end monitor;
}
/
/⽣产者进程
procedure producer;
begin
while(TRUE){
item=produce_item;
ProducerConsumer.insert(item);
}
end;
//消费者进程
进程通信方式procedure consumer;
begin
while(TRUE){
ve;
consume_item(item);
}
end;
关于利⽤信号量解决⽣产者消费者问题的思想可参考博客:
3 MESA管程
本篇第2节重点介绍了Hoare管程,Hoare的思想在于,当P进程唤醒Q进程,P进程切换下CPU由Q进程获取CPU的执⾏权,之后当条件变量满⾜之后P进程再次切换到CPU,从这过程可以看出实际上Hoare管程的多了两次额外的进程切换。为了解决这个问题进⽽引出了MESA 管程。
3.1 MESA管程的机制——wait/notify
Hoare管程是基于wait/signal机制,signal唤醒操作特点是唤醒了某⼀进程并且⽴刻让出CPU的,⽽MES
A管程是基于wait/notify机制的。所谓的notify,是指当⼀个正在管程中的进程执⾏notify©时,它使得c条件队列得到通知,然后发出信号的进程继续执⾏。关于notify 有三点需要注意:
1. notify的结果是实现了位于条件队列头的进程在将来合适且当CPU可⽤时恢复执⾏(区别于Hoare管程,不是⽴即获取CPU执⾏)。
2. 但是由于不能保证在该进程之前没有其它进程进⼊过管程并修改了数据资源,因⽽这个进程在获取CPU执⾏前每次都要重新检查条
件。所以MESA管程⽤while循环取代了Hoare管程中的if语句。
3. ⽤while循环去判断带来的影响是:会导致对条件变量⾄少多⼀次额外的检测。但不再出现额外的进程切换的问题,并且对等待进程在
notify之后何时运⾏没有任何限制。
4. 改进:由于该进程每次被调度时都会由while语句执⾏相关条件检查,所以在实现时可以为每个条件原语关联⼀个监视计时器,不论是
否被通知,超过⼀定等待时间的进程将会被直接设置为就绪态(即使此时条件变量没有满⾜依然不会报
错,因为while语句能保证收到这些收到信号的程序检查相关的变量,如果期望的条件没有满⾜则会继续等待)。
程序清单3-1 MESA管程对于⽣产者消费者问题的解决
monitor ProducerConsumer{
/* 定义两个条件变量,full表⽰缓冲区满了的时候⽣产者停⽌⽣产,empty表⽰缓冲区空了的时候消费者停⽌消费 */
condition full,empty;
/* ⾮空缓冲区的数量 */
integer count;
procedure insert(item :integer);
begin
/* 当缓冲区满了时,⽣产者进程中⽌执⾏,进⼊条件变量full */
while(count==N) then wait(full);
insert_item(item);count++;
/* 当缓冲区数据量为1时,唤醒等待在条件变量empty上的消费者进程 */
cnotify(empty);
end;
function remove:integer;
begin
/* 当缓冲区空了时,消费者进程中⽌执⾏,进⼊条件变量empty */
while(count==0) then wait(empty);
remove=remove_item;count--;
/* 当缓冲区数据量为N-1时,唤醒等待在条件变量full上的⽣产者进程 */
cnotify(full);
end;
end monitor;
}
3.3 MESA管程的机制——wait/broadcast
notify是每次只通知⼀个进程,MESA管程中还实现了每次通知多个进程的机制,即broadcast。
broadcast:使所有在该条件上等待的进程都被释放并进⼊队列。
broadcast使⽤场景:
1. 当⼀个进程不知道有多少进程将被激活时,这种⽅式是⾮常⽅便的。例如:在⽣产者/消费者问题中,假设insert和remove函数都适
⽤于可变长度的字符快,此时,如果⼀个⽣产者往缓冲区添加了⼀批字符,它不需要知道每个正在等待的消费者准备消耗多少字符,⽽broadcast机制可以使得所有正在等待的进程都得到通知并再次尝试运⾏。
2. 当⼀个进程不难以准确判定将激活哪个进程时,也可以使⽤⼴播。
4 进程间通信机制IPC
在上⼀篇和本篇前⼏节已经分别介绍了信号量和管程的机制,但是这两种通信⽅式只能传递很简单的信息,不⽀持⼤量信息的传递,另外管程不适合⽤户多处理器的情况,所以需要引⼊新的通信机制。
4.1 进程间通信⽅式
4.1.1 消息传递
消息传递的核⼼思想如下图所⽰:
程序清单4-1 ⽤PV操作实现send原语
/* 定义如下四个信号量 */
// emptyBuff:空闲的消息缓冲区,初值为N
// fullBuff:填满的消息缓冲区,初值为0
// mutex1:初值为1
// mutex2:初值为1
send(destination,msg){
// 根据destination出接收进程;如果没到直接报错并返回申请空缓冲区P(emptyBuff);
P(mutex1);
取空缓冲区;
V(mutex1);
把消息从msg处复制到空缓冲区;
P(mutex2);
把消息缓冲区挂到接收进程的消息队列;
V(mutex2);
V(fullBuff);
}
4.1.2 共享内存

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。