操作系统(2)—进程管理
一、临界资源:一次仅允许一个进程使用的资源 成为临界资源(包括一些物理设备。访问临界资源的那段代码成为临界区。
二、为了保证临界资源的正确使用,可以把临界资源地访问过成分成四个部分:
- 进入区。为了进入临界区使用临界资源,在进入区要检查可否进入临界区,如果可以进入临界区,则映射之正在访问临界区的标志,以阻止其他进程同时进入临界区。
- 临界区。进程中访问临界资源的那段代码,又称临界段。
- 退出区。将整在访问临界区地标志清除。
- 剩余区。代码中地其余部分。
do{ entry section;//进入区 critical section;//临界区 exit section;//退出区 remainder;//剩余区 }while(true)
三、同步。也称直接制约关系,他是之为完成某种任务而建立的两个或多个进程。这些进程因为需要在某些位置上协调他们的工作次序而等待、传递信息所产生的直接制约关系。进程间地直接制约关系就是源于它们之间地相互合作。
例如进程A通过单缓冲向进程B提供数据。当缓冲区空时,进程B不能获得所需数据而阻塞,一旦进程A将数据送入缓冲区,进程B被唤醒。反之当缓冲区满时,进程A被阻塞,仅当进程B取走缓冲数据时,才能唤醒进程A。
四、互斥。互斥也称间接制约关系。当一个进程进去临界区使用临界资源时,另一个进程必须等待,当占用临界资源的进程退出临界区后,另一进程才允许区访问此临界资源。
例如在仅有一台打印机地系统中有进程A、B,如果进程A需要打印时,系统已将打印机分配给进程B,则进程A必须阻塞。一旦进程B将打印机释放,系统便将进程A唤醒,并将其由阻塞状态变为就绪状态。
五、为禁止两个进程同时进入临界区,同步机制应遵循一下准则:
- 临界区空闲时进程可以进入。
- 已有进程在临界区时,其他试图访问的进程必须等待。
- 有限等待。对请求访问的进程,应保证能在有限时间内进入临界区。
- 让权等待。当进程不能进入临界区时,应立即释放处理器,防止进程忙等待(通俗理解为等待进入临界区的进程一直在霸占着处理机空转)。
六、信号量。信报两级够可用来解决互斥和同步的问题,他只能被两个标准的原语wait(s) 和 signal(s) 来操作,也可记为P、V。
七、整形信号量。 整形信号量被定义为一个用于表示资源数目的整形量S。wait和signal操作课描述为:
wait(s){ //相当于申请资源 while(S<=0); //当S<=0时,忙等待 S = S-1; } signal(S){ //相当于释放资源 S = S+1; }
typedef struct{ int value; struct process *L; }semaphore; void wait(semaphore){ s.value--; if(S.value < 0){ add this process to S.L; block(S.L); //自我阻塞,放弃处理机 } } void signal(semaphore){ s.value++; if(S.value <= 0){ remote a process P from S.L; wakeup(P); //S.value <= 0 表示有进程在等待(被阻塞),所以要wakeup一个等待中的进程。 } }
设S为实现进程P1、P2同步的公共信号量,初值为0。进程P2中的y语句要用到进程P1语句x的运行结果,所以只有当语句x执行完成之后语句y才可以执行。进程同步的算法如下:
若P2线执行到P(S)时,S为0,执行P操作会把进程P2阻塞,并放入阻塞队列中,当进程P1中的x执行完后,执行V操作,把P2从阻塞队列中放回就绪队列,当P2得到处理机时,就可继续执行。
semaphore S = 0; //注意初值 P1(){ ... x; V(S); //S = S + 1 告诉进程P2,语句x已经完成 ... } P2(){ ... P(S); //S = S - 1 检查x是否运行完成 y; //确认无误,运行y语句 ... }
十、利用信号量实现进程互斥。
semaphore S = 1; //初值为1 P1(){ ... P(S); //准备开始访问临界区资源,加锁 进程1的临界区 V(S); //访问结束,解锁 } P2(){ ... P(S); //准备开始访问临界区资源,加锁 进程2的临界区 V(S); //访问结束,解锁 } 在互斥问题中,P、V操作要紧紧的夹着使用互斥资源地那个行为,中间不能有其他冗余代码。
为保证S1 —>S2 S1—>S3 的前驱关系,应分别设置信号量a1 a2 同样为保证S2—>S4 ,S2—>S5,S3—>S6,S4—>S6,S5—>S6,应该设置信号量b1, b2, c, d, e
semaphore a1=a2=b1=b2=c=d=e=0 S1(){ ... V(a1);V(a2); } S2(){ P(a1); ... V(b1);V(b2) } S3(){ P(a2); ... V(c) } S4(){ P(b1); ... V(d) } S5(){ P(b2); ... V(e) } S6(){ P(c);P(d);P(e); }
十二、管程。
- 定义:管程是由一组数据以及定义在这组数据至上的对这组数据地操作组成的软件模块,这组操作能初始化并改变管程中地数据和同步进程。
- 组成:(1)管程的名称;(2)局部于管程地共享数据结构说明。(3)对该数据结构进行操作地一组过程。(4)对局部于管程地共享数据设置初始值的语句。
- 说明:局部于管程的数据结构,只能被局部于管程的过程所访问,任何管程之外的过程都不能访问它;反之,局部于管程的过程也只能访问管程内的数据结构。由此可见,管程相当于围墙,它把共享变量和对它进行操作的若干个过程围了起来,所有进程要访问临界资源时,都必须经过管程才能进入,而管程每次只允许一个进程进入管程,从而实现了进程的互斥(方法是通过调用特定的管程入口进入管程,然后通过管程中的一个过程使用临界资源。当某进程通过调用请求访问某临界资源而未能满足时,管程调用相应同步原语使该进程等待,并将它排在等待队列上。当使用临界资源的进程访问完该临界资源并释放后,管程又调用相应的同步原语唤醒等待队列中的队首进程。为了表示不同的等待原因,设置条件变量,条件变量是与普通变量不同的变量,条件变量不能取任何值,只是一个排队栈。)。
十三、生产者-消费者问题。描述:一组生产者进程和一组消费者进程共享一个初始为空、大小为n的缓冲区,只有缓冲区没满时,生产者才能把消息放入到缓冲区,否则只能等待,只有缓冲区不空时,消费者才能从中取出消息,非则必须等待。由于缓冲区是临界资源,它只允许一个生产者放入消息,或者一个消费者从中取出消息。
semaphore mutex = 1; semaphore full = 0; semaphore empty = n; prodecer(){ while(1){ producer an item in nextp; P(empty); P(mutex); add nextp to buffer; V(mutex); V(full); } } consumer(){ while(1){ P(full); P(mutex); remove an item from buffer; V(mutex); V(empty); consume the item } }注意,如果先P(mutex) 然后在P(empty) / P(full)的话,是不可以的,假设生产者进程已经将缓冲区放满了,消费者进程并没有取出产品,下次到生产者在运行时,他先执行P(mutex),然后执行P(empty)而被阻塞,希望消费者取出产品后将其唤醒。轮到消费者运行时,由于生产者已经封锁了mutex信号量,消费者进程也会被阻塞,会造成死锁。
但是释放的时候先V()谁都可以。
要用谁,先P( )谁。(如果没有可用的,就阻塞,P(mutex)在后,所以不会死锁)
要用谁,先P( )谁。(如果没有可用的,就阻塞,P(mutex)在后,所以不会死锁)
十四、一个较为复杂的生产者—消费者问题。
有一个盘子,每次只能装一个水果,爸爸专向里面装苹果,妈妈向里面装橘子,儿子只吃橘子,女儿只吃苹果。只有盘子为空时,爸爸或妈妈可以向里面放一个水果,只有盘子里有自己想吃的水果时,儿子或女儿才可以从盘子里取出水果。
分析:
- 爸爸和妈妈是互斥关系,儿子和母亲是同步关系,女儿和爸爸时同步关系。儿子和女儿时选择条件执行,不可能并发。
- 这里有4个进程,抽象为两个生产者和两个消费者被连接到大小为1地缓冲区上。
- 信号量设置。plate为互斥信号量,初值为1,表示是否允许放水果(爸爸妈妈互斥)。apple表示盘子中是否有苹果,初值为0, orange表示是否有橘子,初值为0。
semaphore plant = 1; semaphore apple = 0; semaphore orange = 0; dad(){ while(1){ prepare an apple P(plant); //给盘子上锁 put an apple on the plate; V(apple); //放一个apple } } daughter(){ while(1){ P(apple); //取一个qpple take an apple from the plate; V(plate); //给盘子解锁 eat the apple } } mom(){ while(1){ prepare an orange P(plant); put an orange on the plate; V(orange); } } son(){ while(1){ P(orange); take an orange from the plate; V(plate); eat the orange } }
生产者先上锁,再放果。如果反过来的话不能保证有位置放。
消费者先取走,再解锁。如果反过来,会造成死锁。儿子解锁后发现没有apple,造成错误。先P(apple)就能确保不出错。可理解为:制约生产者的是盘子,所以生产者P(plate)(检查盘子是否空)。制约消费者的是水果,消费者要先P(apple)或P(orange)(检查是否有水果)。
要用谁,先P( )谁。
要用谁,先P( )谁。
十五、读者-写者问题。
int count = 0; semaphore mutex = 1; //用于保护更新count变量时地互斥 semaphore rw = 1; //read write 用于保证读者和写者互斥地访问文件 writer(){ while(1){ P(rw); //互斥访问共享文件 Writing; //写入 V(rw); //释放共享文件 } } reader(){ while(1){ P(mutex); //互斥访问count变量 if(count == 0) //当第一个读写进程读共享变量时 P(rw); //阻止写进程写 count++; //读者计数器+1 V(mutex); //释放互斥变量count reading; //读取 P(mutex); //互斥访问count变量 count--; //读者计数器减1 if(count==0) //当最后一个读进程读完共享文件 V(rw); //允许写进程写 V(mutex); //释放互斥变量count } }