1.17k likes | 1.3k Views
4.IPC 经典问题. ( 1 )读者写者问题 有两组并发进程 : 读者和写者 , 共享一组数据区 要求: 允许多个读者同时执行读操作 不允许读者、写者同时操作 不允许多个写者同时操作. 第一类:读者优先. 如果读者来: 1 )无读者、写者,新读者可以读 2 )有写者等,但有其它读者正在读,则新读者也可以读 3 )有写者写,新读者等 如果写者来: 1 )无读者,新写者可以写 2 )有读者,新写者等待 3 )有其它写者,新写者等待. 读者: while (true) { P(mutex);
E N D
4.IPC经典问题 (1)读者写者问题 有两组并发进程: 读者和写者,共享一组数据区 要求: 允许多个读者同时执行读操作 不允许读者、写者同时操作 不允许多个写者同时操作
第一类:读者优先 如果读者来: 1)无读者、写者,新读者可以读 2)有写者等,但有其它读者正在读,则新读者也可以读 3)有写者写,新读者等 如果写者来: 1)无读者,新写者可以写 2)有读者,新写者等待 3)有其它写者,新写者等待
读者: while (true) { P(mutex); readcount ++; if (readcount==1) P (w); V(mutex); 读 P(mutex); readcount --; if (readcount==0) V(w); V(mutex); }; 写者: while (true) { P(w); 写 V(w); }; 第一类读者写者问题的解法
读者: Swait(wmutex,1,1; rcount,R,0); 写; Ssignal(wmutex,1); 写者: Swait(rcount,1,1; wmutex,1,0); 写; Ssignal(rcount,1); 第一类读者写者问题的解法(一般信号量集) • 增加一个限制条件:同时读的“读者”最多R个 • Wmutex表示“允许写”,初值是1 • Rcount表示“允许读者数目”,初值为R
(2)哲学家就餐问题 有五个哲学家围坐在一圆桌旁,桌中央有一盘通心粉,每人面前有一只空盘子,每两人之间放一只筷子 每个哲学家的行为是思考,感到饥饿,然后吃通心粉 为了吃通心粉,每个哲学家必须拿到两只筷子,并且每个人只能直接从自己的左边或右边去取筷子
#define N 5 void philosopher (int i) { while (true) { 思考; 取fork[i]; 取fork[(i+1) % 5]; 进食; 放fork[i]; 放fork[(i+1) % 5]; } }
为防止死锁发生可采取的措施: • 最多允许4个哲学家同时坐在桌子周围 • 仅当一个哲学家左右两边的筷子都可用时,才允许他拿筷子() • 给所有哲学家编号,奇数号的哲学家必须首先拿左边的筷子,偶数号的哲学家则反之 为了避免死锁,把哲学家分为三种状态,思考,饥饿,进食,并且一次拿到两只筷子,否则不拿
哲学家就餐问题解法(1) #define N 5 void philosopher (int i) { while (true) { 思考; 取fork[i]; 取fork[(i+1) % 5]; 进食; 放fork[i]; 放fork[(i+1) % 5]; } }
哲学家就餐问题解法(2) #define N 5 #define THINKING 0 #define HUNGRY 1 #define EATING 2 #typedef int semaphore; int state[N]; semaphore mutex=1; semaphore s[N];
void test(int i) { if (state[ i ] == HUNGRY) && (state [ (i-1) % 5] != EATING) && (state [ (i+1) % 5] != EATING) { state[ i ] = EATING; V(&s[ i ]); } }
void philosopher (int i) { while (true) { 思考; P(&mutex); state[i] = HUNGRY; test(i); V(&mutex); P(&s[i]); 拿左筷子; 拿右筷子; 进食; 放左筷子; 放右筷子; P(&mutex) state[ i ] = THINKING; test([i-1] % 5); test([i+1] % 5); V(&mutex); } } state[ i ] = THINKING s[ i ] = 0
【作业】 1. 推广例子中的消息缓冲问题。 消息缓冲区为k个,有1个发送进程, n个接收进程,每个接收进程对发送来的消息都必须取一次 若有m个发送进程呢?
2.第二类读者写者问题: 写者优先 条件: 1)多个读者可以同时进行读 2)写者必须互斥(只允许一个写者写,也不能读者写者同时进行) 3)写者优先于读者(一旦有写者,则后续读者必须等待,唤醒时优先考虑写者)
3.有一个系统,定义P、V操作如下: P(s): s:=s-1; if s<0 then 将本进程插入相应队列末尾等待; V(s): s:=s+1; if s<=0 then 从相应等待队列队尾唤醒一个进程,将其插入就绪队列;
问题: 1)这样定义P、V操作是否有问题? 2)用这样的P、V操作实现N个进程竞争使用某一共享变量的互斥机制。 3)对于2)的解法,有无效率更高的方法。如有,试问降低了多少复杂性?
4.理发师睡觉问题 理发店里有一位理发师,一把理发椅和N把供等候理发的顾客坐的椅子 如果没有顾客,则理发师便在理发椅上睡觉。当一个顾客到来时,他必须先唤醒理发师 如果顾客到来时理发师正在理发,则如果有空椅子,可坐下来等;否则离开
5.进程的同步机制──管程 管程的提出 采用PV同步机制来编写并发程序,对于共享变量及信号量变量的操作将被分散于各个进程中 缺点: (1)易读性差,因为要了解对于一组共享变量及信号量的操作是否正确,则必须通读整个系统或者并发程序
(2)不利于修改和维护,因为程序的局部性很差,所以任一组变量或一段代码的修改都可能影响全局(2)不利于修改和维护,因为程序的局部性很差,所以任一组变量或一段代码的修改都可能影响全局 (3)正确性难以保证,因为操作系统或并发程序通常很大,要保证这样一个复杂的系统没有逻辑错误是很难的 Dijkstra(1971):“秘书”进程 Hansen和Hoare(1973):管程 进程的同步机制──管程(续1)
管程:一种同步机制 (管程-类程-进程) 管程定义: 指关于共享资源的数据及在其上操作的一组过程或共享数据结构及其规定的所有操作 系统按资源管理的观点分解成若干模块,用数据表示抽象系统资源,同时分析了共享资源和专用资源在管理上的差别,按不同的管理方式定义模块的类型和结构,使同步操作相对集中,从而增加了模块的相对独立性 进程的同步机制──管程(续2)
管程的形式 TYPE monitor_name = MONITOR; 共享变量说明 define 本管程内所定义、本管程外可调用的过程(函数)名字表 use 本管程外所定义、本管程内将调用的过程(函数)名字表 PROCEDURE 过程名(形参表); 过程局部变量说明; BEGIN 语句序列; END; ......
FUNCTION 函数名(形参表):值类型; 函数局部变量说明; BEGIN 语句序列; END; ...... BEGIN 共享变量初始化语句序列; END; 管程的四个组成部分: 名称 数据结构说明 对该数据结构进行操作的一组过程/函数 初始化语句 管程的形式(续)
(一)模块化,一个管程是一个基本程序单位,可以单独编译(一)模块化,一个管程是一个基本程序单位,可以单独编译 (二)抽象数据类型,管程是一种特殊的数据类型,其中不仅有数据,而且有对数据进行操作的代码 (三)信息掩蔽,管程是半透明的,管程中的外部过程(函数)实现了某些功能,管程中的外部过程(函数)实现了某些功能,至于这些功能是怎样实现的,在其外部则是不可见的 管程的三个主要的特性
管程有如下几个要素: (一)管程中的共享变量在管程外部是不可见的,外部只能通过调用管程中所说明的外部过程(函数)来间接地访问管程中的共享变量 (二)为了保证管程共享变量的数据完整性,规定管程互斥进入 (三)管程通常是用来管理资源的,因而在管程中应当设有进程等待队以及相应的等待及唤醒操作 管程的要素
问题:多个进程出现在管程中 当一个进入管程的进程执行等待操作时,它应当释放管程的互斥权;当一个进入管程的进程执行唤醒操作时(如P唤醒Q),管程中便存在两个同时处于活动状态的进程 处理方法有三种: P等待Q继续,直到Q退出或等待√(Hoare) Q等待P继续,直到P等待或退出 规定唤醒为管程中最后一个可执行的操作 管程的实现——问题
因为管程是互斥进入的,所以当一个进程试图进入一个巳被占用的管程时它应当在管程的入口处等待,因而在管程的入口处应当有一个进程等待队列,称作入口等待队列因为管程是互斥进入的,所以当一个进程试图进入一个巳被占用的管程时它应当在管程的入口处等待,因而在管程的入口处应当有一个进程等待队列,称作入口等待队列 如果进程P唤醒进程Q,则P等待Q继续,如果进程Q在执行又唤醒进程R,则Q等待R继续,……,如此,在管程内部,由于执行唤醒操作,可能会出现多个等待进程,因而还需要有一个进程等待队列,这个等待队列被称为紧急等待队列。它的优先级应当高于入口等待队列的优先级 管程的实现——问题(续1)
由于管程通常是用于管理资源的,因而在管程内部,应当存在某种等待机制。当进入管程的进程因资源被占用等原因不能继续运行时使其等待。为此在管程内部可以说明和使用一种特殊类型的变量,称作条件变量: 由于管程通常是用于管理资源的,因而在管程内部,应当存在某种等待机制。当进入管程的进程因资源被占用等原因不能继续运行时使其等待。为此在管程内部可以说明和使用一种特殊类型的变量,称作条件变量: VAR C:condition; 对于条件型变量,可以执行wait和signal操作: 管程的实现——问题(续2)
wait(c):如果紧急等待队列非空,则唤醒第一个等待者;否则释放管程的互斥权,执行此操作的进程的PCB入c链尾部wait(c):如果紧急等待队列非空,则唤醒第一个等待者;否则释放管程的互斥权,执行此操作的进程的PCB入c链尾部 signal(c):如果c链为空,则相当于空操作,执行此操作的进程继续;否则唤醒第一个等待者,执行此操作的进程的PCB入紧急等待队列的尾部 管程的实现——问题(续3)
管程的实现 两个主要途径: * 直接构造 * 间接构造,即用某种已经实现的同步机制去构造 前者效率高 例子:用PV操作构造管程 管程的实现
TYPE one_instance=RECORD mutex:semaphore;(初值1) urgent:semaphore;(初值0) urgent_count:integer;(初值0) END; TYPE monitor_elements=MODULE; define enter,leave,wait,signal; mutex(入口互斥队列) urgent(紧急等待队列) urgent_count(紧急等待队列计数)
PROCEDURE enter(VAR instance:one_instance); BEGIN P(instance.mutex) END;
PROCEDURE leave(VAR instance:one_instance); BEGIN IF instance.urgent_count >0 THEN BEGIN instance.urgent--; V(instance.urgent) END ELSE V(instance.mutex) END;
PROCEDURE wait(VAR instance:one_instance;VAR s:semephore;VAR count:integer); BEGIN count++; IF instance.urgent_count>0 THEN BEGIN instance.urgent_count--; V(instance.urgent) END ELSE V(instance. mutex); P(s); END;
PROCEDURE signal(VAR instance:one_instance; VAR s:semaphore;VAR count:integer); BEGIN IF count>0 THEN BEGIN count--; instance.urgent_count++; V(s); P(instance.urgent) END END;
例子:一个信息缓冲区是一个共享资源 抽象成一个数据结构:数组 构造一些操作(过程或函数) 发送:向缓冲区发消息 接收:从缓冲区取消息 隐藏了内部的数据结构和实现细节
TYPE r_and_w=MODULE; VAR instance:one_instance; rq,wq:semaphore; r_count,w_count:integer; reading_count,write_count:integer; define start_r,finish_r,start_w,finish_w; use monitor_elements.enter, monitor_elements.leave, monitor_elements.wait, monitor_elements.signal;
PROCEDURE start_r; BEGIN monitor_elements.enter(instance); IF write_count>0 THEN monitor_elements.wait (instance,rq,r_count); reading_count++; monitor_elements.signal (instance,rq,r_count); monitor_elements.leave(instance); END;
PROCEDURE finish_r; BEGIN monitor_elements.enter(instance); reading_count--; IF reading_count=0 THEN monitor_elements.signal (instance,wq,w_count); monitor_elements.leave(instance); END;
PROCEDURE start_w; BEGIN monitor_elements.enter(instance); write_count++; IF (write_count>1) OR(reading_count>0) THEN monitor_elements.wait (instance,wq,w_count); monitor_elements.leave(instance); END;
BEGIN reading_count::=0; write_count::=0; r_count:=0; w_count::=0; END;
读者的活动: r_and_w.start_r; 读操作; r_and_w.finish_r; 写者的活动: r_and_w.start_w; 写操作; r_and_w.finish_w;
管程和进程的异同点: (1)设置进程和管程的目的不同 (2)系统管理数据结构 进程:PCB 管程:等待队列 (3)管程被进程调用 (4)管程是操作系统的固有成分,无创建和撤消 管程与进程
四、进程通信 • 概述 • 消息缓冲通信方式
1.概述 P.V操作实现的是进程之间的低级通讯,所以P.V为低级通讯原语。它只能传递简单的信号,不能传递交换大量信息 如果要在进程间传递大量信息则要用Send / Receive原语(高级通讯原语)
(1)进程通信的方式 共享内存: 相互通信的进程间设有公共内存,一组进程向该公共内存中写,另一组进程从公共内存中读,通过这种方式实现两组进程间的信息交换 这种通信模式需要解决两个问题?
进程通信的方式(续1) 消息传递: 系统为进程提供了两个高级通讯原语send和receive send: 当要进行消息传递时执行send receive: 当接收者要接收消息时执行receive
进程通信的方式(续2) 消息传递模式: • 消息缓冲 在内存中开设缓冲区,发送进程将消息送入缓冲区,接收进程接收传递来的缓冲区 • 信箱通信
进程通信的方式(续3) 共享文件模式: 管道通信
直接通信和间接通信 • 直接通信:信息直接传递给接收方,如管道 • 在发送时,指定接收方的地址或标识,也可以指定多个接收方或广播式地址 • 在接收时,允许接收来自任意发送方的消息,并在读出消息的同时获取发送方的地址 • 间接通信:借助于收发双方进程之外的共享数据结构作为通信中转,如消息队列。通常收方和发方的数目可以是任意的
2.消息缓冲 (有界缓冲区原理): 在操作系统空间设置一组缓冲区,当发送进程需要发送消息时,执行send系统调用,产生自愿性中断,进入操作系统,操作系统为发送进程分配一个空缓冲区,并将所发送的消息从发送进程copy到缓冲区中,然后将该载有消息的缓冲区连接到接收进程的消息链链尾,如此就完成了发送过程。发送进程返回到用户态继续执行