收藏 分享(赏)

清华大学操作系统讲义第04讲-经典ipc问题.ppt

上传人:无敌 文档编号:304459 上传时间:2018-03-27 格式:PPT 页数:65 大小:585.50KB
下载 相关 举报
清华大学操作系统讲义第04讲-经典ipc问题.ppt_第1页
第1页 / 共65页
清华大学操作系统讲义第04讲-经典ipc问题.ppt_第2页
第2页 / 共65页
清华大学操作系统讲义第04讲-经典ipc问题.ppt_第3页
第3页 / 共65页
清华大学操作系统讲义第04讲-经典ipc问题.ppt_第4页
第4页 / 共65页
清华大学操作系统讲义第04讲-经典ipc问题.ppt_第5页
第5页 / 共65页
点击查看更多>>
资源描述

1、第 四 讲,操作系统与系统编程,谌 卫 军,清华大学软件学院,2004年春季,【例子2】共享缓冲区的合作进程的同步,设有一个缓冲区buffer,大小为一个字节。Compute进程不断产生字符,送buffer,Print进程从buffer中取出字符打印。如不加控制,会出现多种打印结果,这取决于这两个进程运行的相对速度。在这众多的打印结果中,只有Compute和Print进程的运行刚好匹配的一种是正确的,其它均为错误。,基于信号量的进程同步,解题步骤:分析问题,弄清楚同步关系;设置信号量,说明其含义和初值;写出程序描述。,问题分析:要保证打印结果的正确,Compute和Print进程必须遵循以下同

2、步规则:当Compute把数据送入buffer后,Print才能从buffer中取,否则它必须等待(先存后取);当Print从buffer取走数据后,Compute才能将新数据送buffer,否则也须等待(先取后存),【例子3】 三个并发进程的读写,get进程负责从输入序列f中读取字符,送到输入缓冲区S中;copy进程把输入缓冲区S中的数据复制到输出缓冲区T;put进程从输出缓冲区T中取出数据打印。,输入缓冲区,输出缓冲区,问题分析:为保证三个并发进程的正确运行,必须遵循以下的五条同步规则:在每一个数据上的操作顺序为get - copy - put;只有当s中的旧数据被copy取走后,get才

3、能将新数据送进来,否则就必须等待;只有当get把新数据送到s当中后,copy才能把该数据取走,否则也必须等待;只有当t中的旧数据被put取走后,copy才能把新数据从s拷贝到t当中,否则等待;只有当copy把新数据送入到t中后,put才能把该数据取出打印,否则等待。,流程图1:,流程图2:,semaphore S_Get; / 当前数据的Get操作是否 / 已完成,初值为0semaphore S_Copy_Old; / 上一个数据的Copy操作 / 是否已完成,初值为1semaphore S_Copy_New; / 本次数据的Copy操作是 / 否已完成,初值为0semaphore S_Put

4、; / 上一个数据的Put操作是 / 否已完成,初值为1,信号量的定义,信号量和P、V原语的小结,对信号量和P、V原语的使用可以归纳为三种情形:第一,把信号量视为一个加锁标志位,其目的是为了实现对某个唯一的共享数据的互斥访问,如数据库中的某个记录,各个进程间的某个共享文件。该共享数据的取值与信号量本身的取值并没有什么直接的关系,信号量的作用仅仅是作为一个加锁标志位。其特征是信号量的初始值为1,然后在一个进程内部对它进行配对的P、V操作。,P(mutex); / 申请进入临界区访问该共享数据;V(mutex);/ 退出临界区非临界区,mutex初值为1。,第二,把信号量视为是某种类型的共享资源的

5、剩余个数,其目的是为了实现对这种类型的共享资源的访问,如各种I/O设备。信号量的取值具有实际的意义,就等于空闲资源的个数。多个进程可以同时使用这种类型的资源,直到所有空闲资源均已用完。其特征是信号量的初始值为N(N1),然后在一个进程内部对它进行配对的P、V操作。,P(resource);/ 申请使用一个资源使用该资源;V(resource);/ 释放该资源非临界区,resource的初值为N。,第三,把信号量作为进程间同步的工具,利用它来设定两个进程在运行时的先后顺序。比如说,它可以是某个共享资源的当前个数,但是由一个进程负责生成该资源,而另一个进程负责消费该资源,由此引发了两个进程之间的先

6、后顺序。其特征是信号量的初始值为N(N 0),然后在一个进程里面用对它使用V原语,增加资源个数,而在另外一个进程里面对它使用P原语,减少资源个数,从而实现两个进程之间的同步关系。,配对,先后,2.4 经典的IPC问题,生产者消费者问题 哲学家就餐问题 读者写者问题 睡着的理发师问题,用信号量来解决,主要问题:如何选择信号量,如何安排P、V原语的顺序。,2.4.1 生产者消费者问题,两个进程(生产者和消费者)共享一个公有的、固定大小的缓冲区,生产者不断地制造产品,并把它放入缓冲区,而消费者不断地把产品取出来,并且使用它。要求这两个进程相互协调,正确地完成各自的工作。,问题描述,生产消费者问题,消

7、费者,问题分析,对于生产者进程:制造一个产品,当要送入缓冲区时,要检查缓冲区是否已满,若未满,才可将产品送入缓冲区,并在必要时通知消费者;否则等待;对于消费者进程:当它去取产品时,先要检查缓冲区中是否有产品可取,若有,则取走一个,并在必要时通知生产者;否则等待。这种相互等待,并互通信息就是典型的进程同步。同时,缓冲区是个临界资源,因此,各个进程在使用缓冲区的时候,还有一个互斥的问题。,semaphore S_Buffer_Num;/ 空闲的缓冲区个数,/ 初值为Nsemaphore S_Product_Num;/ 缓冲区当中的产品个/ 数,初值为0semaphore S_Mutex; / 用于

8、互斥访问的信号/ 量,初值为1,信号量的定义,void producer(void) int item; while(TRUE) item = produce_item( );/ 制造一个产品 P(S_Buffer_Num);/ 是否有空闲缓冲区 P(S_Mutex);/ 进入临界区 insert_item(item);/ 产品放入缓冲区 V(S_Mutex);/ 离开临界区 V(S_Product_Num);/ 新增了一个产品 ,生产者进程,void consumer(void) int item; while(TRUE) P(S_Product_Num);/ 缓冲区中有无产品 P(S_Mu

9、tex);/ 进入临界区 item = remove_item( )/ 从缓冲区取产品 V(S_Mutex);/ 离开临界区 V(S_Buffer_Num);/ 新增一个空闲缓冲区 consume_item(item);/ 使用该产品 ,消费者进程,2.4.2 哲学家就餐问题,1965年,由Dijkstra提出并解决,后来逐渐成为该领域的一个经典问题,或者说,是一块试金石,用来试验新的进程同步方法的优劣。,五个哲学家围坐在一张圆桌旁,每个哲学家面前都摆着一大盘意大利面条,面条非常滑,所以每个哲学家都需要两把叉子才能进餐,在相邻两个盘子之间,只有一把叉子。 桌面的布局见右图。,问题描述,本图摘自

10、 “Modern Operating Systems”,0,1,2,3,4,0,1,2,3,4,每个哲学家的动作只有两种:进餐和思考。当一个哲学家感到饥饿时,他试图去获得他左边和右边的两把叉子(一次取一把,顺序无关),然后才能开始进餐。吃完以后,他需要把两把叉子放回原处,然后继续思考。问题是:如何保证哲学家们的动作有序进行?如:不出现相邻者同时要求进餐,也不出现有人永远拿不到叉子。,问题描述(续),方案1,#define N 5 / 哲学家个数void philosopher(int i) / 哲学家编号:0 4 while(TRUE) think( );/ 哲学家在思考 take_fork(

11、i);/ 去拿左边的叉子 take_fork(i + 1) % N);/ 去拿右边的叉子 eat( );/ 吃面条中. put_fork(i);/ 放下左边的叉子 put_fork(i + 1) % N);/ 放下右边的叉子 ,不正确,可能导致死锁。,方案2,while(1)/ 去拿两把叉子 take_fork(i);/ 去拿左边的叉子 if(fork(i+1)%N) / 右边叉子还在吗 take_fork(i + 1) % N);/ 去拿右边的叉子 break;/ 两把叉子均到手 else / 右边叉子已不在 put_fork(i);/ 放下左边的叉子 wait_some_time( );/

12、 等待一会儿 ,对拿叉子的过程进行了改进,但仍不正确,方案3,while(1)/ 去拿两把叉子 take_fork(i);/ 去拿左边的叉子 if(fork(i+1)%N) / 右边叉子还在吗 take_fork(i + 1) % N);/ 去拿右边的叉子 break;/ 两把叉子均到手 else / 右边叉子已不在 put_fork(i);/ 放下左边的叉子 wait_random_time( );/ 等待随机长时间 ,等待时间随机变化。可行,但非万全之策,方案4,semaphore mutex / 互斥信号量,初值1void philosopher(int i) / 哲学家编号i:04 w

13、hile(TRUE) think( );/ 哲学家在思考 P(mutex);/ 进入临界区 take_fork(i);/ 去拿左边的叉子 take_fork(i + 1) % N);/ 去拿右边的叉子 eat( );/ 吃面条中. put_fork(i);/ 放下左边的叉子 put_fork(i + 1) % N);/ 放下右边的叉子 V(mutex);/ 退出临界区 ,互斥访问。正确,但每次只允许一人进餐,方案4的缺点:它把就餐(而不是叉子)看成是必须互斥访问的临界资源,因此会造成(叉子)资源的浪费。从理论上说,如果有五把叉子,应允许两个不相邻的哲学家同时进餐。,S1 思考中S2 进入饥饿状

14、态;S3 如果左邻居或右邻居正在进餐,等待;否则转S4S4 拿起两把叉子;S5 吃面条S6 放下左边的叉子;S7 放下右边的叉子;S8 新的一天又开始了,转S1,哲学家就餐问题的解答,指导原则:要么不拿,要么就拿两把叉子。,S1 思考中S2 进入饥饿状态;S3 如果左邻居或右邻居正在进餐,进入阻塞状态; 否则转S4S4 拿起两把叉子;S5 吃面条S6 放下左边的叉子,看看左邻居现在能否进餐 (饥饿状态、两把叉子都在),若能则唤醒之;S7 放下右边的叉子,看看右邻居现在能否进餐, 若能,唤醒之;S8 新的一天又开始了,转S1,指导原则:不能浪费CPU时间;进程间相互通信。,必须有一个数据结构,来

15、描述每个哲学家的当前状态;该数据结构是一个临界资源,各个哲学家对它的访问应该互斥地进行进程互斥;一个哲学家吃饱后,可能要唤醒它的左邻右舍,两者之间存在着同步关系进程同步;,数据结构的定义,#define N5/ 哲学家个数#define LEFT(i+N-1)%N/ 第i个哲学家的左邻居#define RIGHT(i+1)%N/ 第i个哲学家的右邻居#define THINKING0/ 思考状态#define HUNGRY1/ 饥饿状态#define EATING2/ 进餐状态int stateN;/ 记录每个人的状态semaphore mutex;/ 互斥信号量,初值1semaphore s

16、N;/ 每人一个信号量,0,void philosopher(int i) / i的取值:0到N1 while(TRUE)/ 封闭式开发,一直循环 think( ); / 思考中 take_forks(i);/ 拿到两把叉子或被阻塞 eat( );/ 吃面条中 put_forks(i);/ 把两把叉子放回原处 ,函数philosopher的定义,/ 功能:要么拿到两把叉子,要么被阻塞起来。void take_forks(int i) / i的取值:0到N1 P(mutex);/ 进入临界区 statei = HUNGRY;/ 我饿了! test(i); / 试图拿两把叉子 V(mutex);/

17、 退出临界区 P(si);/ 没有叉子便阻塞,函数take_forks的定义,void test(int i) / i的取值:0到N1 if(statei = HUNGRY / 第i人可以吃饭了 ,函数test的定义,/ 功能:把两把叉子放回原处,并在需要的时候,/ 去唤醒左邻右舍。void put_forks(int i) / i的取值:0到N1 P(mutex);/ 进入临界区 statei = THINKING;/ 交出两把叉子 test(LEFT); / 看左邻居能否进餐 test(RIGHT);/ 看右邻居能否进餐 V(mutex);/ 退出临界区,函数put_forks的定义,2.

18、4.3 读者写者问题,在一个航空定票系统当中,有很多个竞争的进程想要访问(读、写)系统的数据库。访问规则是:在任何时候,可以允许多个进程同时来读,但如果有一个进程想要更新 (写)该数据库,则其他的任何进程都不能访问,包括读者和写者。问题是:怎么样来编程实现读者和写者。,问题描述,问题分析,任何时候“写者”最多只允许一个,而“读者”可以有多个: “读写”是互斥的; “写写”是互斥的; “读读”是允许的;,基于读者优先策略的方法,假设读者来:1)若有其它读者在读,则不论是否有写者在等, 新读者都可以读(读者优先);2)若无读者、写者,则新读者也可以读;3)若无读者,且有写者在写,则新读者等待;假设

19、写者来:1)若有读者,则新写者等待;2)若有其它写者,则新写者等待;3)若无读者和写者,则新写者可以写;,需要设置一个计数器rc,用来记录并发 运行的读者个数; 对于各个读者而言,该计数器是一个临 界资源,对它的访问必须互斥进行,因 此设置一个互斥信号量S_mutex; 对于各个写者而言、写者与所有的读者 而言,数据库是一个临界资源,对它的 访问必须互斥地进行,因此设置一个互 斥信号量S_db。,int rc = 0;/ 并发读者的个数semaphore S_mutex;/ 对rc的互斥信号量,初值1semaphore S_db;/ 对数据库的信号量,初值1,读者写者问题的一个解答,void

20、writer(void) while(TRUE) think_up_data( ); / 生成数据,非临界区 P(S_db); / 希望访问数据库 write_data_base( ); / 更新数据库 V(S_db); / 退出临界区 ,void reader(void) while(TRUE) P(S_mutex); / 互斥地访问计数器rc rc +; / 新增了一个读者 if(rc = 1) P(S_db); / 如果是第一个读者 V(S_mutex); / 退出对rc的访问 read_data_base( ); / 读取数据库的内容 P(S_mutex); / 互斥地访问计数器rc

21、rc -; / 减少一个读者 if(rc = 0) V(S_db); / 如果是最后一个读者 V(S_mutex); / 退出对rc的访问 use_data_read( ); / 使用数据,非临界区 ,对于基于读者优先策略的方法,只要有一个读者处于活动状态,后来的读者都会被接纳。如果读者源源不断地出现的话,那么写者就始终处于阻塞状态。,2.4.4 睡着的理发师问题,理发店里有一位理发师和一把理发椅,还有 N 张座椅供等候的顾客休息。在没有顾客的时候,理发师就会躺在理发椅上睡觉。因此,当有顾客来到理发店时,他必须先叫醒理发师。如果理发师正在理发时又有顾客来到,此时,如果有空椅子可坐,他们就会坐下

22、来等;如果没有空椅子,就会离开。问题:用信号量和P、V原语写出理发师和顾客行为的程序描述。,问题描述,睡着的理发师问题,(本图摘自Andrew S. Tanenbaum: “Modern Operating Systems” ),S1 判断有多少人在等待, 有多少把椅子,若椅子 不够,转S5;S2 如果是第一个顾客,就 去叫醒理发师;S3 走到一把空椅子处坐下 等,等理发师叫自己;S4 理发中;S5 离开理发店。,问题分析,顾客的做法,S1 判断是否有人在等, 若没有,就去睡觉; 若有,转S2;S2 去唤醒一个正在等 待的顾客;S3 理发中;S4 转S1。,理发师的做法,需要设置一个整型的wa

23、iting变量,来 记录正在等待的顾客个数; 对于理发师和各个顾客而言,waiting 变量是一个临界资源,对它的访问必须 互斥地进行,因此需要设置一个互斥信 号量S_mutex; 顾客可能需要去唤醒理发师,理发师也 需要去唤醒顾客,两者之间存在着同步 关系,所以需要两个信号量S_customer 和S_barber。,#define CHAIRS 5 / 椅子的个数int waiting = 0; / 等待的顾客个数semaphore S_mutex; / 互斥信号量,初值为1semaphore S_customer; / 等待的顾客数,初值0semaphore S_barber; / 空闲

24、的理发师个数,0,睡着的理发师问题,void barber(void) while(TRUE) P(S_customer); / 若无顾客,睡去 P(S_mutex); / 申请对waiting的访问 waiting=waiting-1; / 等待的顾客数减1 V(S_barber); / 理发师有空(唤醒顾客) V(S_mutex); / 结束对waiting的访问 cut_hair( ); / 给顾客理发 ,理发师进程,void customer(void) P(S_mutex); / 申请对waiting的访问 if(waiting CHAIRS) waiting=waiting+1;

25、/ 等待的顾客数加1 V(S_customer); / 新增一顾客(唤醒理发师) V(S_mutex); / 结束对waiting的访问 P(S_barber); / 理发师有空否? get_haircut( ); / 理发中 else V(S_mutex); / 理发店满员了,走先 ,顾客进程,2.4.5 管程(Monitor),信号量方法的缺点,使用困难:各并发进程间的逻辑关系比较复杂, 在使用信号量时,必须小心谨慎,稍有不当(如 P、V操作的次序错误、重复或遗漏)就会出错; 出错后果严重:如竞争状态、死锁和各种无法预 料的、不可重现的错误现象; 可读性差:信号量的控制分布在整个程序中,其

26、 正确性分析很困难; 维护困难:各模块的独立性差,任一组变量或一 段代码的修改都可能影响全局。,void producer(void) int item; while(TRUE) item = produce_item( ); P(S_Buffer_Num); P(S_Mutex); insert_item(item); V(S_Mutex); V(S_Product_Num); ,两个P操作写反了导致死锁,void consumer(void) int item; while(TRUE) P(S_Product_Num); P(S_Mutex); item = remove_item( );

27、 V(S_Mutex); V(S_Buffer_Num); consume_item(item); ,P(S_Mutex); / 写反了 P(S_Buffer_Num);,为了更容易地编写出正确的程序,Hoare(1974)和Hansen(1975)提出了管程(monitor)的概念。其基本思想是:将共享变量以及对共享变量所进行的操作封装在一个模块中。,管程是由一组变量、数据结构和函数所构成的一种特殊的软件模块。,管程的引入,管程的定义:,monitor example integer i; condition c; procedure producer( ); end; procedure

28、consumer( ); end;end monitor;,封装性:进程可以调用管程当中的函数, 但是不能直接访问管程的内部数据结构; 互斥性:在任何时候,只允许一个进程在 管程中活动(即调用管程的函数); 语言相关性:管程属于编程语言的范畴, 由编译器来识别并实现对管程内函数的互 斥访问。,管程的性质:,互斥:把共享数据封装在管程内,把各个 进程的临界区变成管程当中的函数。这样 就能够保证任何两个进程都不会同时进入 它们的临界区; 同步:在管程内部,应当具有某种等待机 制。当进入管程的进程因资源被占用等原 因不能继续运行时应使其等待。因此在管 程内部可以声明和使用一种特殊类型的变 量-条件变

29、量(condition variables)。,基于管程的进程同步与互斥:,条件变量:用来描述等待的原因。每个条件变量 表示一种等待的原因,它不取具体的数值。对条 件变量的操作:wait和signal; 当一个管程函数发现它不能继续执行下去时,使 用wait操作,使得调用它的进程进入阻塞状态, 同时允许其他的进程进入管程;而signal操作的 作用是唤醒某个正在等待的进程; 通过wait和signal操作即可实现进程间同步关系; wait操作和signal操作有点类似于P、V原语,但 条件变量不取具体的数值,不进行信号的累加, 因此在signal操作发出信号前,必须有wait操作在 等待,否则

30、该信号就丢失了。,条件变量,问题:当一个进程 P 用signal操作唤醒另外一个进程Q时,如何避免两个进程同时位于管程当中呢?,处理方法有三种:P等待,Q先执行,直到它退出管程或者再次被阻塞;P继续执行,等它退出管程后Q再执行;P在执行signal操作后立即退出管程,即把signal作为管程函数的最后一条语句。,基于管程的生产者消费者问题,2.5 进程间高级通信,低级通信:只能传递状态和整数值(控制信息),包括用来实现进程同步和互斥的信号量和管程机制。优点是速度快。缺点是:传送信息量小:每次通信传递的信息量固定,若需要传递较多信息,就得进行多次通信。编程复杂:用户需要直接去实现通信的细节,编程

31、复杂,容易出错。高级通信:能够传送任意数量的数据,包括三类:共享内存、管道、消息。,低级通信和高级通信,2.5.1 共享内存,绝大多数现代的操作系统都提供了相应的方法,来让各个进程共享它们地址空间当中的某些部分,即共享内存。在共享内存中,可以任意读写和使用任意的数据结构(缓冲区)。一组进程向共享内存中写,另一组进程从共享内存中读,通过这种方式实现两组进程间的信息交换。类似于生产者消费者问题中的缓冲区,需要进程的同步和互斥机制来确保数据的一致性。,2.5.2 管道(Pipe),管道通信由UNIX首创,由于其有效性,后来的 一些系统相继引入了管道技术; 管道通信以文件系统为基础,所谓管道即连接两

32、个进程之间的一个打开的共享文件,专用于进程 之间的数据通信; 发送进程从管道的一端写入数据流,接收进程从 管道的另一端按先进先出的顺序读出数据; 在对管道文件进行读写过程中,发送进程和接收 进程要进行正确的同步和互斥,由系统自动完成,2.5.3 消息(message),消息机制是由操作系统来维护的,进程在通信时 无须使用共享变量,而是以消息作为信息载体; 消息机制提供了两个原语操作(系统调用): send(message),消息大小定长或不定长; receive(message); 如果进程P和Q想要进行通信,它们需要: 在两者之间建立一个通信链路; 使用send和receive交换信息。,直

33、接通信:通信双方必须指明与之通信的对象。 send(P, message):发送一条消息给进程P; receive(Q, message):从进程Q那里接收一条 消息。如果没有收到消息,可以阻塞起来等 待消息的到来,也可以立即返回; 操作系统维护着一个消息缓冲区,当一条消 息已经被发送,但还没有被接收时,系统自 动把它放在缓冲区当中。,消息传递模式,间接通信:进程间通过一个中间媒介信箱来 实现通信。当两个进程需要通信时,就创建一 个共享的信箱,通过该信箱来发送和接收消息。 send(A, message)发送一条消息到信箱A; receive(A, message)从信箱A接收一条消息;,消息传递模式(续),下次课将介绍进程的调度问题,请提前预习相关的内容,即教材上的第2.5小节。,下 课 啦 !,

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高等教育 > 大学课件

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报