OS知识点汇总(考研用)——第二章:进程管理(下)

 本文参考于《2021年操作系统考研复习指导》(王道考研),《计算机操作系统教程》
思维导图:

文章目录

  • OS知识点汇总(考研用)——第二章:进程管理(下)
    • 2.进程管理
      • 2.3 进程同步
        •  2.3.1 进程同步的基本概念
          •   1.临界资源
          •   2.同步
          •   3.互斥
        •  2.3.2 实现临界区互斥的基本方法
          •   1.软件实现方法
          •   2.硬件实现方法
        •  2.3.3 信号量
          •   1.整型信号量
          •   2.记录型信号量
          •   3.利用信号量实现同步
          •   4.利用信号量实现进程互斥
          •   5.利用信号量实现前驱关系
          •   6.分析进程同步和互斥问题的方法步骤
        •  2.3.4 管程
          •   1.管程的定义
          •   2.条件变量
        •  2.3.5 经典同步问题
          •   1.生产者-消费者问题
          •   2.读者-写者问题
          •   3.哲学家进餐问题
          •   4.吸烟者问题
      • 2.4 死锁
        •  2.4.1 死锁的概念
          •   1.死锁的定义
          •   2.死锁产生的原因
        •  2.4.2 死锁的处理策略
          •   1.死锁预防
          •   2.避免死锁
          •   3.死锁的检测及解除
        •  2.4.3 死锁预防
          •   1.破坏互斥条件
          •   2.破坏不剥夺条件
          •   3.破坏请求并保持条件
          •   4.破坏循环等待条件
        •  2.4.4 死锁避免
          •   1.系统安全状态
          •   2.银行家算法
          •   3.安全性算法举例
          •   4.银行家算法举例
        •  2.4.5 死锁检测和解除
          •   1.资源分配图
          •   2.死锁定理
          •   3.死锁解除

2.进程管理

2.3 进程同步

 2.3.1 进程同步的基本概念

  在多道程序环境下,进程是并发执行的,不同进程之间存在着不同的相互制约关系。为了协调进程之间的相互制约关系,引入了进程同步的概念。例如,让系统计算1+2x3,假设系统产生两个进程:一个是加法进程,一个是乘法进程。要让计算结果正确,要让加法进程发生在乘法进程之后,但实际上OS具有异步性,若不加以制约,加法进程发生在乘法进程之前是绝对有可能的,因此要制定一定的机制去约束加法进程,让它在乘法进程完成之后才发生。

  1.临界资源

  虽然多个进程可以共享系统中的各种资源,但其中许多资源一次只能为一个进程所用,将一次仅允许一个进程使用的资源称为临界资源。 许多物理设备都属于临界资源,如打印机。此外,还有许多变量、数据等都可以被若干进程共享,也属于临界资源。
  对临界资源的访问,必须互斥地进行,在每个进程中,访问临界资源的那段代码称为临界区。 为了保证临界资源的正确使用,可把临界资源的访问过程分成4个部分:
  (1)进入区
  为了进入临界区使用临界资源,在进入区要检查可否进入临界区,若能进入临界区,则应设置正在访问临界区的标志,以阻止其他进程同时进入临界区
  (2)临界区
  进程中访问临界资源的那段代码,又称临界段
  (3)退出区
  将正在访问临界区的标志清除
  (4)剩余区
  代码中的其余部分

do{entry section;        //进入区critical section;  //临界区exit section;      //退出区remainder section; //剩余区
}while(true)
  2.同步

  同步亦称直接制约关系,是指为完成某种任务而建立的两个或多个进程,这些进程因为需要在某些位置上协调它们的工作次序而等待、传递信息所产生的制约关系。 进程间的直接制约关系源于它们之间的相互合作。
  例如,输入进程A通过单缓冲向进程B提供数据。当该缓冲区为空时,进程B不能获得所需数据而阻塞,一旦进程A将数据送入缓冲区,进程B就被唤醒。反之,当缓冲区满时,进程A被阻塞,仅当进程B取走缓冲数据时,才唤醒进程A。


  例题:在OS中,要对并发进程进行同步的原因是()
  A.进程必须在有限的时间内完成
  B.进程具有动态性
  C.并发进程是异步的
  D.进程具有结构性

  答案:C;


  3.互斥

  互斥也称间接制约关系。当一个进程进入临界区使用临界资源时,另一个进程必须等待,当占用临界资源的进程退出临界区后,另一进程才允许去访问此临界资源。
  例如,在仅有一台打印机的系统中,有两个进程A和进程B,若进程A需要打印时,系统已将打印机分配给进程B,则进程A必须阻塞。一旦进程B将打印机释放,系统便将进程A唤醒,并将其由阻塞态变为就绪态。

  为禁止两个进程同时进入临界区,同步机制应遵循以下准则:
  (1)空闲让进
  临界区空闲时,可以允许一个请求进入临界区的进程立即进入临界区
  (2)忙则等待
  当已有进程进入临界区时,其他试图进入临界区的进程必须等待
  (3)有限等待
  对请求访问的进程,应保证能在有限时间内进入临界区
  (4)让权等待
  当进程不能进入临界区时,应立即释放处理器,防止进程忙等待


  例题:进程之间存在哪几种制约关系?各是什么原因引起的?以下活动各属于哪种制约关系
  (1)若干学生去图书馆借书
  (2)两队进行篮球比赛
  (3)流水线生产的各道工序
  (4)商品生产和消费

  答案:进程之间存在两种制约关系,即同步与互斥
  同步是由于并发进程之间需要协调完成同一个任务时引起的一种关系,是一个进程等待另一个进程向它直接发送消息或数据时的一种制约关系
  互斥是由并发进程之间竞争系统的临界资源引起的,是一个进程等待另一个进程已经占有的必须互斥使用的资源时的一种制约关系
  (1)互斥,同一本书只能被一名学生借阅,或任何时刻只能有一名学生借阅一本书
  (2)互斥,篮球是互斥资源,只可被一个队获得
  (3)同步,一个工序完成后开始下一个工序
  (4)同步,生产商品后才能消费


  例题:有两个并发进程P1、P2,其程序代码如下:

P1()                                 P2()
{                                       {x=1;                                  x=-1;                                                              y=2;                                   a=x+3;if(x>0)                                  x=a+x;z=x+y;                                b=a+x;else                                    c=b*b;z=x*y;                              print c;print z;                            }
}

  (1)可能打印出的z值有多少(假设每条赋值语句是一个原子操作)
  (2)可能打印出的c值有多少(其中x为P1和P2的共享变量)

  答案:(1)-2,1,2,3,5,7;(2)9,25,81


 2.3.2 实现临界区互斥的基本方法

  1.软件实现方法

  在进入区设置并检查一些标志来标明是否有进程在临界区中,若已有进程在临界区,则在进入区通过循环检查进行等待,进程离开临界区后则在退出区修改标志。
  (1)算法一:单标志法

P0进程:                         P1进程:
while( turn!=0 );                  while( turn!=1 );
critical section;                   critical section;
turn=1;                                turn=0;
remainder section;                  remainder section;

  该算法设置一个公用整型变量turn,用于指示被允许进入临界区的进程编号,即若turn=0,则允许P0进程进入临界区。该算法可确保每次只允许一个进程进入临界区。但两个进程必须交替进入临界区,若某个进程不再进入临界区,则另一个进程也将无法进入临界区(违背“空闲让进”)。 若P0顺利进入临界区并从临界区离开,则此时临界区是空闲的,但P1并没有进入临界区的打算,turn=1一直成立,P0就无法再次进入临界区
  (2)算法二:双标志法先检查

Pi进程:                         Pj进程:
while( flag[j] );   /1              while( flag[i] );   /2
flag[i]=TRUE;      /3              flag[j]=TRUE;      /4
critical section;                   critical section;
flag[i]=FALSE;                     flag[j]=FALSE;
remainder section;                  remainder section;

  该算法的基本思想是在每个进程访问临界区资源之前,先查看临界资源是否正在被访问,若正被访问,该进程需等待;否则,进程才进入自己的临界区。 为此,设置一个数据flag[i],如第i个元素值为FALSE,表示Pi进程未进入临界区,值为TRUE,表示Pi进程进入临界区。
  优点:不用交替进入,可连续使用;
  缺点:Pi和Pj可能同时进入临界区,按序列1,2,3,4执行时,会同时进入临界区(违背“忙则等待”)。即在检查对方的flag后和切换自己的flag前有一段时间,结果都检查通过。这里的问题出在检查和修改操作不能一次进行。
  (3)算法三:双标志法后检查

Pi进程:                         Pj进程:
flag[i]=TRUE;                      flag[j]=TRUE;
while(flag[j]);                     while(flag[i]);
critical section;                   critical section;
flag[i]=FALSE;                     flag[j]=FALSE;
remainder section;                  remainder section;

  算法二先检测对方的进程状态标志,再置自己的标志,由于在检测和放置中可插入另一个进程到达时的检测操作,会造成两个进程在分别检测后同时进入临界区。为此,算法三先将自己的标志设置为TRUE,再检测对方的状态标志,若对方标志为TRUE,则进程等待;否则进入临界区。
  两个进程几乎同时都想进入临界区时,它们分别将自己的标志值flag设置为TRUE,并且同时检测对方的状态,发现对方也要进入临界区时,双方互相谦让,结果谁也进不了临界区,从而导致饥饿现象。
  (4)算法四:Peterson’s Algorithm

Pi进程:                         Pj进程:
flag[i]=TRUE;                      flag[j]=TRUE;
turn=j;                                turn=i;
while(flag[j]&&turn==j);          while( flag[i]&&turn==i );
critical section;                   critical section;
flag[i]=FALSE;                     flag[j]=FALSE;
remainder section;                  remainder section;

  为了防止两个进程为进入临界区而无限期等待,又设置了变量turn,每个进程在先设置自己的标志后再设置turn标志。这时,再同时检测另一个进程状态标志和不允许进入标志,以便保证两个进程同时要求进入临界区时,只允许一个进程进入临界区。
  具体如下:考虑进程Pi,一旦设置flag[i]=true,就表示它想要进入临界区,同时turn=j,此时若进程Pj已在临界区中,符合进程Pi中的while循环条件,则Pi不能进入临界区。若Pj不想要进入临界区,即flag[j]=false,循环条件不符合,则Pi可以顺利进入,反之亦然。本算法的基本思想是算法一和算法三的结合。 利用flag解决临界资源的互斥访问,而利用turn解决饥饿现象 (若两个进程几乎同时都想进入临界区,最后turn的值要么是i,要么是j,而两个flag都为TRUE,此时总有一个进程可以执行,而另一个进程不能执行,克服了算法三中的饥饿现象)


  Peterson’s Algorithm可类比为两个人进门,每个人进门前都会和对方客套一句“你先走”。如果进门时没别人,就当和空气说句废话,然后大步登门入室;如果两个人同时进门,就互相请先,但各自只客套一次,所以先客套的人请完对方,就等着对方请自己,然后光明正大地进门



  例题:

  答案:
  


  2.硬件实现方法

  计算机提供了特殊的硬件指令,允许对一个字中的内容进行检测和修正,或对两个字的内容进行交换等。 通过硬件支持实现临界段问题的方法称为低级方法,或称元方法。
  (1)中断屏蔽方法

.
.
.
关中断;
临界区;
开中断;
.
.
.

  当一个进程正在使用处理机执行它的临界区代码时,防止其他进程进入其临界区进行访问的最简方法是,禁止一切中断发生,或称之为屏蔽中断,关中断。 因为CPU只在发生中断时引起进程切换,因此屏蔽中断能够保证当前运行的进程让临界区代码顺利地执行完,进而保证互斥的正确实现,然后执行开中断。
  这种方法限制了处理机交替执行程序的能力,因此执行的效率会明显降低。对内核来说,在它执行更新变量或列表的几条指令期间,关中断是很方便的,但将关中断的权力交给用户则很不明智,若一个进程关中断后不再开中断,则系统可能会因此终止。

  (2)硬件指令方法
  TestAndSet指令:这条指令是原子操作,即执行该代码时不允许被中断。其功能是读出指定标志后把该标志设置为真。 指令的功能描述如下:

boolean TestAndSet(boolean *lock)
{boolean old;old=*lock;*lock=true;return old;
}

  可为每个临界资源设置一个共享布尔变量lock,表示资源的两种状态:true表示正被占用,初值为false。在进程访问临界资源之前,利用TestAndSet检查和修改标志lock;若有进程在临界区,则重复检查,直到进程退出。 利用该指令实现进程互斥的算法描述如下:

while TestAndSet(&lock);
进程的临界区代码段;
lock=false;
进程的其他代码;

  Swap指令: 该指令的功能是交换两个字(字节)的内容。其功能描述如下:

Swap(boolean *a,boolean *b)
{boolean temp;temp=*a;*a=*b;*b=temp;
}

  对TestAndSet和Swap指令的描述仅是功能实现,而并非软件实现的定义,事实上,它们是由硬件逻辑直接实现的,不会被中断。


  应为每个临界资源设置一个共享布尔变量lock,初值为false;在每个进程中再设置一个局部布尔变量key,用于与lock交换信息。在进入临界区前,先利用Swap指令交换lock与key的内容,然后检查key的状态;有进程在临界区时,重复交换和检查过程,直到进程退出。 利用Swap指令实现进程互斥的算法描述如下:

key=true;
while( key!=false )Swap(&lock,&key);
进程的临界区代码段;
lock=false;
进程的其他代码;

  硬件方法的优点:适用于任意数目的进程,而不管是单处理机还是多处理机;简单、容易验证其正确性。可以支持进程内有多个临界区,只需为每个临界区设立一个布尔变量。
  硬件方法的缺点:进程等待进入临界区时要耗费处理机时间,不能实现让权等待。从等待进程中随机选择一个进入临界区,有的进程可能一直选不上,从而导致饥饿现象。


  以上的所有代码实现只是为了表述进程实现同步和互斥的过程,并不是说计算机内部实现同步互斥的就是这些代码


 2.3.3 信号量

  信号量机制是一种功能较强的机制,可用来解决互斥与同步问题,它只能被两个标准的原语wait(S)和signal(S)访问,也可记为P操作和V操作。
  原语是指完成某种功能且不被分割、不被中断执行的操作序列,通常可由硬件来实现。 例如,前述的TestAndSet和Swap指令就是由硬件实现的原子操作。原语功能的不被中断执行特性在单处理机上可由软件通过屏蔽中断方法实现。
  原语之所以不能被中断执行,是因为原语对变量的操作过程若被打断,可能会去运行另一个对同一变量的操作过程,从而出现临界段问题。若能够找到一种解决临界段问题的元方法,就可实现对共享变量操作的原子性。

  1.整型信号量

  整型信号量被定义为一个用于表示资源数目的整型量S,wait和signal操作可描述为:

wait(S)
{while( S<=0 );S=S-1;
}
signal(S)
{S=S+1;
}

  wait操作中,只要信号量S<=0,就会不断地测试。因此,该机制并未遵循让权等待的准则,而是使进程处于忙等的状态。

  2.记录型信号量

  记录型信号量是不存在忙等现象的进程同步机制。除需要一个用于代表资源数目的整型变量value外,再增加一个进程链表L,用于链接所有等待该资源的进程。 记录型信号量得名于采用了记录型的数据结构。记录型信号量可描述为:

typedef struct
{int value;struct process *L;
}semaphore;

  相应的wait(S)和signal(S)的操作如下:

void wait( semaphore S )     //相当于申请资源
{S.value--;if(S.value<0){add this process to S.L;block(S.L);}
}

  wait操作,S.value- -表示进程请求一个该类资源,当S.value<0时,表示该类资源已分配完毕,因此进程应调用block原语,进行自我阻塞,放弃处理机,并插入该类资源的等待队列S.L,可见该机制遵循了让权等待的准则。

void signal( semaphore S )       //相当于释放资源
{S.value++;if( S.value<=0 ){remove a process P from S.L;wakeup( P );}
}

  signal操作,表示进程释放一个资源,使系统中可供分配的该类资源数增1,因此有S.value++。若加1后仍是S.value<=0,则表示在S.L中仍有等待该资源的进程被阻塞,因此还应调用wakeup原语,将S.L中的第一个等待进程唤醒。


  例题:一个进程因在互斥信号量mutex上执行V(mutex)操作而导致唤醒另一个进程时,执行V操作后mutex的值为()
  A.大于0  B.小于0
  C.大于等于0  D小于等于0

  答案:D;


  3.利用信号量实现同步

  信号量机制能用于解决进程间的各种同步问题。设S为实现进程P1,P2同步的公共信号量,初值为0。进程P2中的语句y要使用进程P1中语句x的运行结果,所以只有当语句x执行完成之后语句y才可以执行。

semaphore S=0;          //初始化信号量
P1()
{x;                 //语句xV(S);              //告诉进程P2,语句x已经完成...
}
P2()
{...P(S);               //检查语句x是否运行完成y;                 //检查无误,运行y语句...
}

  若P2先执行到P(S)时,S为0,执行P操作会把进程P2阻塞,并放入阻塞队列;当进程P1中的x执行完后,执行V操作,把P2从阻塞队列中放回就绪队列,当P2得到处理机时,就得以继续执行

  4.利用信号量实现进程互斥

  设S为实现进程P1,P2互斥的信号量,由于每次只允许一个进程进入临界区,所以S的初值应该为1(即可用资源数为1)。 只需把临界区置于P(S)和V(S)之间,即可实现两个进程对临界资源的互斥访问。

semaphore S=1;              //初始化信号量
P1()
{...P( S );                 //准备开始访问临界资源,加锁进程P1的临界区;          V( S );                 //访问结束,解锁...
}
P2()
{...P( S );                 //准备开始访问临界资源,加锁进程P2的临界区;          V( S );                 //访问结束,解锁...
}

  当没有进程在临界区时,任意一个进程要进入临界区,就要执行P操作,把S的值减为0,然后进入临界区;当有进程存在于临界区时,S的值为0,再有进程要进入临界区,执行P操作时将会被阻塞,直至在临界区中的进程退出,这样便实现了临界区的互斥。
  互斥是不同进程对同一信号量进行P,V操作实现的,一个进程成功对信号量进行了P操作后进入临界区,并在退出临界区后,由该进程本身对该信号量执行V操作,表示当前没有进程进入临界区,可以让其他进程进入。

  同步互斥中的P,V操作:在同步问题中,若某个行为要用到某种资源,则在这个行为前面P这种资源一下;若某个行为会提供某种资源,则在这个行为后面V这种资源一下。在互斥问题中,P,V操作要紧夹使用互斥资源的那个行为,中间不能有其他冗余代码。

  5.利用信号量实现前驱关系

  信号量也可用来描述程序之间或语句之间的前驱关系。

  S1,S2,S3,…,S6是最简单的程序段(只有一条语句)。为使各程序段能正确执行,应设置若干初始值为0的信号量。为保证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);                         //S1已经运行完成V(a2);
}
S2()
{P(a1);                         //检查S1是否运行完成...;V(b1);                          //S2已经运行完成V(b2);
}
S3()
{P(a2);                         //检查S1是否运行完成...;V(c);                           //S3已运行完成
}
S4()
{P(b1);                         //检查S2是否已经运行完成...;V(d);                         //S4已经运行完成
}
S5()
{   P(b2);                          //检查S2是否已经运行完成...;V(e);                         //S5已经运行完成
}
S6()
{P(c);                          //检查S3是否已经运行完成P(d);                         //检查S4是否已经运行完成P(e);                         //检查S5是否已经运行完成...;
}

  例题:
  答案:

P1()
{P(S1);从输入设备输入数据a;V(S2);P(Sb);x=a+b;P(Sy);P(Sz);使用打印机打印出x,y,z的结果;
}
P2()
{P(S2);从输入设备输入数据b;V(S3);V(Sb);y=a*b;V(Sy);V(Sy);
}
P3()
{P(S3);从输入设备输入数据c;P(Sy);z=y+c-a;V(Sz);
}

  注意:(1)P1、P2、P3不应加while(1),题中的过程只进行一次;为控制数据的依次输入,设S1,S2,S3,其中S1的初值为1,P1第一次执行P(S1)时不会被阻塞,之后P1再次执行P(S1)时被阻塞,整个后续过程也不再进行。S2和S3的初值为0。由于有了S1,S2,S3,它们会保证对输入设备的使用不会同时进行,因此不用再对输入设备的使用增设信号量
  (2)为控制计算过程的顺序,设Sb,Sy,Sz,初值均为0;在计算完y后,为使P1和P3都继续执行,需执行两次V(Sy)的操作;打印机打印数据应是将xyz一次性都打印出来,在打印操作之前应有P(Sy),P(Sz);

  例题:

  答案:

semaphore close_door=0,stop_car=0;
driver()
{while(1){P(close_door);启动车辆;正常行车;到站停车;V(stop_car);}
}
seller()
{while(1){关车门;V(close_door);售票;P(stop_car);开车门;上下乘客;}
}

  注意:(1)售票和启动车辆并不构成同步关系,没有必要设置信号量;(2)售票员进程的最前两句必须是关车门和唤醒司机进程,否则会死锁


  6.分析进程同步和互斥问题的方法步骤

  (1)关系分析
  找出问题中的进程数,并分析它们之间的同步和互斥关系
  (2)整理思路
  根据进程的操作流程确定P操作、V操作的大致顺序
  (3)设置信号量
  设置需要的信号量,确定初值

 2.3.4 管程

  在信号量机制中,每个要访问临界资源的进程都必须自备同步的PV操作,大量分散的同步操作给系统管理带来了麻烦,且容易因操作不当而导致系统死锁。于是,便产生了一种新的进程同步工具——管程。管程的特性保证了进程互斥,无须程序员自己实现互斥,从而降低了死锁发生的可能性。同时管程提供了条件变量,可以让程序员灵活地实现进程同步。

  1.管程的定义

  系统中的各种硬件资源和软件资源,均可用数据结构抽象地描述其资源特性,即用少量信息和对资源所执行的操作来表征该资源,而忽略它们的内部结构和实现细节。
  利用共享数据结构抽象地表示系统中的共享资源,而把对该数据结构实施的操作定义为一组过程。进程对共享资源的申请、释放等操作,都通过这组过程来实现,这组过程还可以根据资源情况,或接受或阻塞进程的访问,确保每次仅有一个进程使用共享资源,这样就可以统一管理对共享资源的所有访问,实现进程互斥。这个代表共享资源的数据结构,以及由对该共享数据结构实施操作的一组过程所组成的资源管理程序,称为管程(monitor)。 管程定义了一个数据结构和能为并发过程所执行(在该数据结构上)的一组操作,这组操作能同步进程和改变管程中的数据。
  管程由4部分组成:
  (1)管程的名称
  (2)局部于管程内部的共享结构数据说明
  (3)对该数据结构进行操作的一组过程(或函数)
  (4)对局部于管程内部的共享数据设置初始值的语句

  管程的定义描述举例如下:

monitor Demo     //1.定义一个名称为Demo的管程
{共享数据结构S;   //2.定义共享数据结构,对应系统中的某种共享资源init_code()     //4.对共享数据结构初始化的语句{S=5;     //初始资源数等于5}take_away()      //3.过程1:申请一个资源{对共享数据结构x的一系列处理;S--;       //可用资源数-1...}give_back()        //3.过程2:归还一个资源{对共享数据结构x的一系列处理;S++;        //可用资源数+1...}
}

  管程很像一个类,在某种意义上反映了面向对象程序设计的思想
  (1)管程把对共享资源的操作封装起来
  管程内的共享数据结构只能被管程内的过程所访问。一个进程只有通过调用管程内的过程才能进入管程访问共享资源。对于上例,外部进程只能通过调用take_away()过程来申请一个资源;归还资源也一样
  (2)每次仅允许一个进程进入管程,从而实现进程互斥
  若多个进程同时调用take_away(),give_back(),则只有某个进程运行完它调用的过程后,下个进程才能开始运行它调用的过程。即各个进程只能串行执行管程内的过程,这一特性保证了进程互斥访问共享数据结构S


  例题:以下关于管程的叙述中,错误的是()
  A.管程是进程同步工具,解决信号量机制大量同步操作分散的问题
  B.管程每次只允许一个进程进入管程
  C.管程中signal操作的作用和信号量机制中的V操作相同
  D.管程是被进程调用的,管程是语法范围,无法创建和撤销

  答案:C;管程中的signal操作与信号量中的V操作不同,信号量机制中的 V操作一定会改变信号量的值,使S=S+1。而管程中的signal操作是针对某个条件变量的,若不存在因该条件而阻塞的进程,则signal不会产生任何影响

  例题:何谓管程?管程由几部分组成?说明引入管程的必要性

  答案:当共享资源用共享数据结构表示时,资源管理程序可用对该数据结构进行操作的一组过程来表示,如资源的请求和释放过程request和release。把这样一组相关的数据结构和过程一并归为管程。Hansan为管程所下的定义是:一个管程定义了一个数据结构和能为并发进程所执行(在该数据结构上)的一组操作,这组操作能同步进程和改变管程中的数据。
  管程由三部分组成:
  (1)局部于管程的共享变量说明
  (2)对该数据结构进行操作的一组过程
  (3)对局部于管程的数据设置初始值的语句;此外,还需为管程命名
  管程的引入是为了解决临界区分散所带来的管理和控制问题。在没有管程之前,对临界区的访问分散在各个进程之中,不易发现和纠正分散在用户程序中的不正确使用PV操作等问题。管程将这些分散在各进程中的临界区集中起来,并加以控制和管理,管程一次只允许一个进程进入管程内,从而既便于系统管理共享资源,又能保证互斥。


  2.条件变量

  当一个进程进入管程后被阻塞,直到阻塞的原因解除时,在此期间,如果该进程不释放管程,那么其他进程无法进入管程。为此,将阻塞原因定义为条件变量condition。 通常,一个进程被阻塞的原因可以有多个,因此在管程中设置了多个条件变量。每个条件变量保存了一个等待队列,用于记录因该条件变量而阻塞的所有进程,对条件变量只能进行两种操作,即wait和signal。
  x.wait:当x对应的条件不满足时,正在调用管程的进程调用x.wait将自己插入x条件的等待队列,并释放管程。此时其他进程可以使用该管程。
  x.signal:x对应的条件发生了变化,则调用x.signal,唤醒一个因x条件而阻塞的进程。

monitor Demo
{共享数据结构S;condition x;       //定义一个条件变量xinit_code(){...}take_away(){if( S<=0 )x.wait();      //资源不够,在条件变量x上阻塞等待资源足够,分配资源,做一系列相应处理;}give_back(){归还资源,做一系列相应处理;if( 有进程在等待 )x.signal();     //唤醒一个阻塞进程}
}

  条件变量和信号量的比较:
  相似点:条件变量的wait/signal操作类似于信号量的P/V操作,可以实现进程的阻塞/唤醒
  不同点:条件变量是没有值的,仅实现了排队等待功能;而信号量是有值的,信号量的值反映了剩余资源数,而在管程中,剩余资源数用共享数据结构记录。


  例题:若x是管程内的条件变量,则当进程执行x.wait()时所做的工作是()
  A.实现对变量x的互斥访问
  B.唤醒一个在x上阻塞的进程
  C.根据x的值判断该进程是否进入阻塞态
  D.阻塞该进程,并将之插入x的阻塞队列中

  答案:D;在同一时刻,管程中只能有一个进程在执行。若进程A执行了x.wait()操作,则该进程会阻塞,并挂到条件变量x对应的阻塞队列上。这样,管程的使用权被释放,就可以有另一个进程进入管程。若进程B执行了x.signal()操作,则会唤醒x对应的阻塞队列的队首进程。


 2.3.5 经典同步问题

  1.生产者-消费者问题

  问题描述:一组生产者进程和一组消费者进程共享一个初始为空、大小为n的缓冲区,只有缓冲区没满时,生产者才能把消息放入缓冲区,否则必须等待;只有缓冲区不空时,消费者才能从中取出消息,否则必须等待。 由于缓冲区是临界资源,它只允许一个生产者放入消息,或一个消费者从中取出消息。
  问题分析:
  (1)关系分析:
  生产者和消费者对缓冲区互斥访问是互斥关系,同时生产者和消费者又是一个相互协作的关系,只有生产者生产之后,消费者才能消费,它们也是同步关系。
  (2)整理思路:
  需要解决互斥和同步PV操作的位置
  (3)信号量设置:
  信号量mutex作为互斥信号量,用于控制互斥访问缓冲池,互斥信号量初值为1;信号量full用于记录当前缓冲池中的满缓冲区数,初值为0;信号量empty用于记录当前缓冲池中的空缓冲区数,初值为n

semaphore mutex=1;          //临界区互斥信号量
semaphore empty=n;         //空闲缓冲区
semaphore full=0;          //缓冲区初始化为空
producer()                  //生产者进程
{while( 1 ){produce an item in nextp;       //生产数据P(empty);   (要用什么,P一下)  //获取空缓冲区单元P(mutex);    (互斥夹紧)      //进入临界区add nextp to buffer;  (行为)   //将数据放入缓冲区V(mutex);     (互斥夹紧)      //离开临界区,释放互斥信号量V(full);  (提供什么,V一下) //满缓冲区数加1}
}
consumer()                  //消费者进程
{while(1){P(full);          //获取满缓冲区单元P(mutex);         //进入临界区remove an item from buffer;      //从缓冲区中取出数据V(mutex);            //离开临界区,释放互斥信号量V(empty);         //空缓冲区数加1consume the item;  //消费数据}
}

  该类问题要注意对缓冲区大小为n的处理,当缓冲区中有空时,便可对empty变量执行P操作,一旦取走一个产品便要执行V操作以释放空闲区。对empty和full变量的P操作必须放在对mutex的P操作之前


  若生产者进程先执行P(mutex),然后执行P(empty),消费者进程执行P(mutex),然后执行P(full),可不可以?
  不可。若生产者进程已将缓冲区放满,消费者进程并没有取产品,即empty=0,当下次仍然是生产者进程运行时,它先执行P(mutex)封锁信号量,再执行P(empty)时将被阻塞,希望消费者取出产品后将其唤醒。轮到消费者进程运行时,它先执行P(mutex),然而由于生产者进程已经封锁mutex信号量,消费者进程也会被阻塞,这样一来生产者、消费者进程都被阻塞,都指望对方唤醒自己,因此陷入了无休止的等待。同理,若消费者已将缓冲区取空,即full=0,下次若还是消费者先运行,也会出现类似的死锁。不过生产者释放信号量时,mutex,full先释放哪一个无所谓,消费者先释放mutex或empty都可以。


  另一个稍复杂的生产者-消费者问题:
  问题描述:
  桌子上有一个盘子,每次只能向其中放入一个水果。爸爸专向盘子中放苹果,妈妈专向盘子中放橘子,儿子专等吃盘子中的橘子,女儿专等吃盘子中的苹果。只有盘子为空时,爸爸或妈妈才可向盘子中放一个水果;仅当盘子中有自己需要的水果时,儿子或女儿可以从盘子中取出水果
  (1)关系分析:
  由每次只能向其中放入一个水果可知,爸爸和妈妈是互斥关系。爸爸和女儿、妈妈和儿子是同步关系,而且这两对进程必须连起来,儿子和女儿之间没有同步和互斥关系,因为他们是选择条件执行,不可能并发

  (2)整理思路:
  这里有4个进程,实际上可抽象为两个生产者和两个消费者被连接到大小为1的缓冲区上。
  (3)信号量设置
  首先将信号量plate设置为互斥信号量,表示是否允许向盘子中放入水果,初值为1表示允许放入,且只允许放入一个。信号量apple表示盘子中是否有苹果,初值为0表示盘子为空,不许取,apple=1表示可以取。信号量orange表示盘子中是否有橘子,初值为0表示盘子为空,不许取,orange=1表示可以取

semaphore plate=1,apple=0,orange=0;
dad()               //父亲进程
{while(1){prepare an apple;P(plate);    //互斥向盘中取、放水果put the apple on the plate;     //向盘中放苹果V(apple);   //允许取苹果}
}
mom()               //母亲进程
{while(1){prepare an orange;P(plate);   //互斥向盘中取、放水果put the orange on the plate;    //向盘中放橘子V(orange);  //允许取橘子}
}
son()               //儿子进程
{while(1){P(orange);            //互斥向盘中取橘子take an orange from the plate;V(plate);           //允许向盘中取、放水果eat the orange;}
}
daughter()          //女儿进程
{while(1){P(apple);         //互斥向盘中取苹果take an apple from the plate;V(plate);            //允许向盘中取、放水果eat the apple;}
}

  dad()和daughter()、mom()和son()必须连续执行,正因为如此,也只能在女儿拿走苹果后或儿子拿走橘子后才能释放盘子,即V(plate)操作


  例题:三个进程P1、P2、P3互斥使用一个包含N(N>0)个单元的缓冲区。P1每次用produce()生成一个正整数并用put()送入缓冲区某一空单元;P2每次用getodd()从该缓冲区中取出一个奇数并用countodd()统计奇数个数;P3每次用geteven()从该缓冲区中取出一个偶数并用counteven()统计偶数个数。请用信号量机制实现三个进程的同步与互斥活动,并说明所定义的信号量的含义(要求用伪代码描述)

  答案:

semaphore mutex=1;          //缓冲区操作互斥信号量
semaphore odd=0,even=0;       //奇数、偶数进程的同步信号量
semaphore empty=N;         //空缓冲区单元个数信号量
cobegin
{Process P1(){while(1){x=produce();    //生成一个数P(empty);        //判断缓冲区是否有空单元P(mutex);      //缓冲区是否被占用Put();            V(mutex);       //释放缓冲区if( x%2==0 )V(even);   //若是偶数,向P3发出信号elseV(odd);        //若是奇数,向P2发出信号}}Process P2(){while(1){P(odd);            //收到P1发来的信号,已产生一个奇数P(mutex);     //缓冲区是否被占用getodd();V(mutex);        //释放缓冲区V(empty);        //向P1发信号,多出一个空单元countodd();}}Process P3(){while(1){P(even);      //收到P1发来的信号,已产生一个偶数P(mutex);     //缓冲区是否被占用geteven();V(mutex);       //释放缓冲区V(empty);        //向P1发信号,多出一个空单元countevev();}}
}coend

  需注意的地方有:
  (1)P1、P2、P3访问缓冲区是互斥的,应设mutex;缓冲区为空时消费者不能访问缓冲区,应设empty;P1、P2以及P1、P3有同步关系,应设odd,even;注意这里不用再设full了,odd、even已经具有了full的作用,P(odd)成功,表明一定有一个奇数放入了缓冲区
  (2)访问缓冲区是互斥的,应用PV包起来,V(even)和V(odd)可以放到临界区中,先释放,也可放在临界区外,后释放;但countodd()和counteven()与互斥无关,不应放在临界区中

  例题:在一个仓库中可以存放A和B两种产品,要求
  (1)每次只能存入一种产品
  (2)A产品数量-B产品数量<M
  (3)B产品数量-A产品数量<N
  其中,M,N是正整数,试用PV操作描述产品A与产品B的入库过程

  答案:

semaphore mutex=1,AminueB=M-1,BminusA=N-1;
PA()                                PB()
{                                   {while(1)                           while(1){                                   {P(AminusB);                            P(BminusA);P(mutex);                            P(mutex);放A;                             放B;V(BminusA);                           V(AminusB);V(mutex);                            V(mutex);}                                  }
}                                   }

  例题:某工厂有两个生产车间和一个装配车间,两个生产车间分别生产A,B两种零件,装配车间的任务是把A,B两种零件组装成产品,两个生产车间每生产一个零件后,都要分别把它们送到装配车间的货架F1,F2上。F1存放零件A,F2存放零件B,F1和F2的容量均可存放10个零件。装配工人每次从货架上取一个零件A和一个零件B后组装成产品。请用PV操作进行正确管理。

  答案:

semaphore F1_empty=10,F1_full=0,F1_mutex=1;
semaphore F2_empty=10,F2_full=0,F2_mutex=1;
producerA()
{while(1){生产一个A零件;P(F1_empty);P(F1_mutex);将A放入F1;V(F1_full);V(F1_mutex);}
}
producerB()
{while(1){生产一个B零件;P(F2_empty);P(F2_mutex);将B放入F2;V(F2_full);V(F2_mutex);}
}
Assemble()
{while(1){P(F1_full);P(F1_mutex);拿走一个零件A;V(F1_empty);V(F1_mutex);P(F2_full);P(F2_mutex);拿走一个零件B;V(F2_empty);V(F2_mutex);组装;}
}

  例题:某寺庙有小和尚、老和尚若干,有一水缸,由小和尚提水入缸供老和尚饮用。水缸可容10桶水,水取自同一井中。水井狭窄,每次只能容一个桶取水。水桶总数为3个。每次入缸取水仅为一桶水,且不可同时进行。试给出有关从缸取水、入水的算法描述

  答案:

semaphore well=1;
semaphore vat=1;
semaphore empty=10;
semaphore full=0;
semaphore pail=3;
old_monk()
{while(1){P(full);P(pail);P(vat);从水缸中打一桶水;V(vat);V(empty);喝水;V(pail);}
}
young_monk()
{while(1){P(empty);P(pail);P(well);从井中打一桶水;V(well);P(vat);将水倒入水缸中;V(vat);V(full);V(pail);}
}

  注意:(1)从缸中取水,喝水两个动作应是分开的;同理,从井中取水,将水倒入缸中两个动作也是分开的;(2)注意将水倒入缸中应和从缸中取水互斥

  例题:
  答案:

semaphore empty=10;
semaphore mutex=1;
semaphore full=0;
semaphore service=0;
cobegin
{Process 顾客 i{P(empty);P(mutex);从取号机上取号;V(mutex);V(full);P(service);接收服务;}Process 营业员{while( TRUE ){P(full);V(empty);V(service);为顾客服务;}}
coend

  注意:(1)service的初值为0,因为提供服务的主体是营业员,顾客执行P(service)后应被阻塞,由营业员执行V(service)将顾客唤醒。(2)顾客离开座位的原因是被叫到号码,因此V(empty)应由营业员执行

  例题:
  答案:

semaphore empty=N;
semaphore wheel=0;
semaphore frame=0;
semaphore s1=N-2;
semaphore s2=N-1;
worker1()
{do{加工一个车架;P(s1);P(empty);车架放入箱中;V(frame);}while(1);
}
worker2()
{do{加工一个车轮;P(s2);P(empty);车轮放入箱中;V(wheel);}while(1);
}
worker3()
{do{P(frame);箱中取一车架;V(empty);V(s1);P(wheel);P(wheel);箱中取两车轮;V(empty);V(empty);V(s2);V(s2);组装为一台车;}while(1);
}

  注意:(1)题中没有强制要求对箱子的访问互斥,因此不必设对箱子访问的互斥量mutex;(2)箱子中轮子最多N-1,车架最多N-2,否则会死锁;(3)设置了表示轮子最大值、车架最大值的信号量后,还需设表示空位置的信号量empty,s1=N-2,s2=N-1仅是为了检验是否超出最大量,并不能表示是否还有空位置,empty=N必不可少

  例题:设PQR共享一个大小为1的缓冲区,PQ构成一对生产者-消费者,R既为生产者又为消费者。使用PV操作实现其同步
  答案:

semaphore full=0;
semaphore empty=1;
semaphore mutex=1;
P()
{while(1){P(empty);P(mutex);Product one;V(mutex);V(full);}
}
Q()
{while(1){P(full);P(mutex);consume one;V(mutex);V(empty);}
}
R()
{if(empty==1){P(empty);P(mutex);Product one;V(mutex);V(full);}if( full==1 ){P(full);P(mutex);consume one;V(mutex);V(empty);}
}

  注意:R既为生产者又为消费者,由于缓冲区大小为1,因此必须在执行前判断其状态(若其角色不对,将直接被阻塞,运行R也就没有意义),若empty==1,则执行生产者功能,若full = =1,则执行消费者功能

  例题:系统中有多个生产者进程和多个消费者进程,共享一个能存放1000件产品的环形缓冲区(初始为空)。缓冲区未满时,生产者进程可以放入其生产的一件产品,否则等待;缓冲区未空时,消费者进程可从缓冲区取走一件产品,否则等待。要求一个消费者进程从缓冲区连续取出10件产品后,其他消费者进程才可以取产品。请使用信号量PV操作实现进程间的互斥与同步,要求写出完整的过程,并说明所用信号量的含义和初值

  答案:

semaphore mutex1=1;
semaphore mutex2=1;
semaphore empty=n;
semaphore full=0;
producer()
{while(1){生产一个产品;P(empty);P(mutex2);把产品放入缓冲区;V(mutex2);V(full);}
}
consumer()
{while(1){P(mutex1);for( int i=0;i<10;i++ ){P(full);P(mutex2);从缓冲区取出一件产品;V(mutex2);V(empty);消费这件产品;}V(mutex1);}
}

  注意:应设置两个不同的信号量来分别控制单个消费者连续访问缓冲区以及对缓冲区访问的互斥,若只设对缓冲区访问互斥的信号量,将其提到for循环之前,则缓冲区只能被消费者连续访问,将造成死锁。

  例题:

  答案:

semaphore empty_A=M-x,full_A=x,empty_B=N-y,full_B=y,mutex_A=1,mutex_B=1;
A{while(1){P(full_A);P(mutex_A);从A的信箱中取出一个邮件;V(mutex_A);V(empty_A);回答问题并提出一个新问题;P(empty_B);P(mutex_B);将新邮件放入B的信箱;V(mutex_B);V(full_B);}
}
B{while(1){P(full_B);P(mutex_B);从B的信箱中取出一个邮件;V(mutex_B);V(empty_B);回答问题并提出一个新问题;P(empty_A);P(mutex_A);将新邮件放入A的信箱;V(mutex_A);V(full_A);}
}

  2.读者-写者问题

  问题描述:
  有读者和写者两组并发进程,共享一个文件,当两个或以上的读进程同时访问共享数据时不会产生副作用,但若某个写进程和其他进程(读进程或写进程)同时访问共享数据时则可能导致数据不一致的错误。因此要求:(1)允许多个读者可以同时对文件执行读操作; (2)只允许一个写者往文件中写信息; (3)任一写者在完成写操作之前不允许其他读者或写者工作 (4)写者执行写操作前,应让已有的读者和写者全部退出
  问题分析:
  (1)关系分析:
  读者和写者互斥,写者和写者互斥,读者和读者不互斥
  (2)整理思路:
  两个进程,即读者和写者。写者是比较简单的,它和任何进程互斥,用互斥信号量的PV操作即可解决。读者较复杂,它必须在实现与写者互斥的同时,实现与其他读者的同步, 因此简单的一对PV操作是无法解决问题的。这里用到了一个计数器,用它来判断当前是否有读者读文件。 当有读者时,写者是无法写文件的,此时读者会一直占用文件,当没有读者时,写者才可以写文件。同时,这里不同读者对计数器的访问也应该是互斥的。
  (3)信号量设置:
  首先设置信号量count为计数器,用于记录当前读者的数量,初值为0;设置mutex为互斥信号量,用于保护更新count变量时的互斥;设置互斥信号量rw,用于保证读者和写者的互斥访问

int count=0;        //用于记录当前的读者数量
semaphore mutex=1; //用于保护更新count变量时的互斥
semaphore rw=1;        //用于保证读者和写者互斥地访问文件
writer()            //写者进程
{while(1){P(rw);        //互斥访问共享文件writing;  //写入V(rw);      //释放共享文件}
}
reader()            //读者进程
{while(1){P(mutex); //互斥访问count变量if( count==0 )       //当第一个读进程读共享文件时P(rw);           //阻止写进程写count++;          //读者计数器加1V(mutex);  //释放互斥变量countreading;   //读取P(mutex);   //互斥访问count变量count--;           //读者计数器减1if( count==0 )       //当最后一个读进程读完共享文件V(rw);          //允许写进程写V(mutex);   //释放互斥变量count}
}

  在上述算法中,读进程是优先的,即当存在读进程时,写操作将被延迟,且只要有一个读进程活跃,随后而来的读进程都将被允许访问文件。 这样的方式会导致写进程可能长时间等待,且存在写进程饿死的情况。

  若希望写进程优先,即当有读进程正在读共享文件时,有写进程请求访问,这时应禁止后续读进程的请求,等到已在共享文件的读进程执行完毕,立即让写进程执行,只有在无写进程执行的情况下才允许读进程再次运行。为此,增加一个信号量并在上面程序的writer()和reader()函数中各增加一对PV操作,就可以得到写进程优先的解决程序:

int count=0;        //用于记录当前的读者数量
semaphore mutex=1; //用于保护更新count变量时的互斥
semaphore rw=1;        //用于保证读者和写者互斥地访问文件
semaphore w=1;     //用于实现写优先
writer()            //写者进程
{while(1){P(w);     //在无写进程请求时进入P(rw);      //互斥访问共享文件writing;  //写入V(rw);      //释放共享文件V(w);       //恢复对共享文件的访问}
}
reader()            //读者进程
{while(1){  P(w);       //在无写进程请求时进入P(mutex);   //互斥访问count变量if( count==0 )       //当第一个读进程读共享文件时P(rw);           //阻止写进程写count++;          //读者计数器加1V(mutex);  //释放互斥变量countV(w);      //恢复对共享文件的访问reading;    //读取P(mutex);   //互斥访问count变量count--;           //读者计数器减1if( count==0 )       //当最后一个读进程读完共享文件V(rw);          //允许写进程写V(mutex);   //释放互斥变量count}
}

  这里的写进程优先是相对而言的,有的书上把这个算法称为读写公平法, 即读写进程具有一样的优先级。当一个写进程访问文件时,若先有一些读进程要求访问文件,后有另一个写进程要求访问文件,则当前访问文件的进程结束对文件的写操作时,会是一个读进程而不是一个写进程占用文件(在信号量w的阻塞队列上,因为读进程先来,因此排在阻塞队列队首,而V操作唤醒进程时唤醒的是队首进程),所以说这里的写优先是相对的。
  读者-写者问题有一个关键的特征,即有一个互斥访问的计数器count,因此遇到一个不太好解决的同步互斥问题时,要想一想用互斥访问的计数器count能否解决问题

  例题:
  答案:
(1)

semaphore bridge=1;
NtoS()
{P(bridge);通过桥;V(bridge);
}
StoN()
{P(bridge);通过桥;V(bridge);
}

  桥上每次只能有一辆车行驶,所以只要设置一个信号量bridge就可判断桥是否能使用,若在使用中,等待;若无人使用,执行P操作进入,出桥后执行V操作
(2)

int countSN=0;
int countNS=0;
semaphore mutexSN=1;
semaphore mutexNS=1;
semaphore bridge=1;
StoN()
{P(mutexSN);if( countSN==0 )P(bridge);countSN++;V(mutexSN);过桥;P(mutexSN);countSN--;if( countSN==0 )V(bridge);V(mutexSN);
}
NtoS()
{P(mutexNS);if( countNS==0 )P(bridge);countNS++;V(mutexNS);过桥;P(mutexNS);countNS--;if( countNS==0 )V(bridge);V(mutexNS);
}

  注意:用于获得桥使用权的if语句不能写为if( countNS == 0 && countSN==0),如果无NS车,有SN车,将直接跳过if,达不到阻塞效果。按上述if语句执行,若无NS车,有SN车,NtoS执行P(bridge)时将被阻塞

  例题:

  答案:

semaphore mutex_y1=1;
semaphore mutex_y2=1;
semaphore mutex_z=1;
thread1
{cnum w;P(mutex_y1);w=add(x,y);V(mutex_y1);...
}
thread2
{cnum w;P(mutex_y2);P(mutex_z);w=add(y,z);V(mutex_z);V(mutex_y2);...
}
thread3
{cnum w;w.a=1;w.b=1;P(mutex_z);z=add(z,w);V(mutex_z);P(mutex_y1);P(mutex_y2);y=add(y,w);V(mutex_y1);V(mutex_y2);...
}

  注意:线程3对z写,线程2对z读,它们应对z互斥访问,设mutex_z;线程3对y写,线程1,2分别对y读,它们应对y互斥访问,设mutex_y1,mutex_y2;不应只对y的访问设置一个信号量,若只设一个信号量,读y的操作就不能并行了


  3.哲学家进餐问题

  问题描述:
  一张圆桌边上坐着5名哲学家,每两名哲学家之间的桌上摆一根筷子,两根筷子中间是一碗米饭。哲学家在思考时,并不影响他人。只有当哲学家饥饿时,才试图拿起左、右两根筷子(一根一根地拿起)。若筷子已在他人手上,则需要等待。饥饿的哲学家只有同时拿到了两根筷子才可以开始进餐,进餐完毕后,放下筷子继续思考

  问题分析:
  (1)关系分析
  5名哲学家与左右邻居对其中间筷子的访问是互斥关系
  (2)整理思路
  解决方法有两个:一是让他们同时拿两根筷子;二是对每名哲学家的动作制定规则,避免饥饿或死锁现象发生
  (3)信号量设置
  定义互斥信号量数组chopstick[5]={1,1,1,1,1},用于对5个筷子的互斥访问。哲学家按顺序编号为0~4,哲学家i左边筷子的编号为i,哲学家右边筷子的编号为(i+1)%5。

  错误算法:

semaphore chopstick[5]={1,1,1,1,1};
Pi()
{do{P( chopstick[i] );P( chopstick[(i+1)%5] );eat;V( chopstick[i] );V( chopstick[(i+1)%5] );thick;}while(1);
}

  当5名哲学家都想要进餐并分别拿起左边的筷子时(都恰好执行完wait(chopstick[i]) ),筷子已被拿光,等到他们再想拿右边的筷子时(执行wait(chopstick[(i+1)%5]) ),就全被阻塞,因此出现了死锁。

  为防止死锁产生,可对哲学家进程施加一些限制条件,比如:
  (1)至多允许四名哲学家同时进餐
  至多只允许四名哲学家同时去拿左筷子,最终能保证至少有一位哲学家能进餐。

semaphore r=4;
P(r);
P( chopstick[i] );
P( chopstick[(i+1)%5] );
eat;
V( chopstick[i] );
V( chopstick[(i+1)%5] );
V(r);

  (2)对哲学家顺序编号,要求奇数号哲学家先拿左边的筷子,然后再拿右边的筷子,而偶数号哲学家则刚好相反

  (3)仅当一名哲学家左右两边的筷子都可用时,才允许他抓起筷子
  即每次都使一个哲学家同时拿到两根筷子

semaphore chopstick[5]={1,1,1,1,1};     //初始化信号量
semaphore mutex=1;                     //设置取筷子的信号量
Pi()                                    //i号哲学家的进程
{do{P(mutex);       //在取筷子前获得互斥量P( chopstick[i] );          //取左边筷子P( chopstick[(i+1)%5] );    //取右边筷子V(mutex);        //释放取筷子的信号量eat;         //进餐V( chopstick[i] );          //放回左边筷子V( chopstick[(i+1)%5] );   //放回右边筷子think;          //思考}while(1);
}

  哲学家进餐问题的思想其实和贪心算法的思想截然相反。 贪心算法强调争取眼前认为最好的,而不考虑后续会有什么后果。在哲学家进餐问题中,若只要眼前有筷子就拿起,就会出现死锁。然而,若不仅考虑眼前的一步,而且考虑下一步,即不因为有筷子能拿起就拿起,而是考虑能不能一次拿起两根筷子才做决定的话,就会避免死锁问题。


  例题:有n(n>=3)名哲学家围坐在一张圆桌边,每名哲学家交替地就餐和思考。在圆桌中心有m(m>=1)个碗,每两名哲学家之间有一根筷子。每名哲学家必须取到一个碗和两侧的筷子后,c才能就餐,进餐完毕,将碗和筷子放回原位,并继续思考。为使尽可能多的哲学家同时就餐,且防止出现死锁现象,请使用信号量的PV操作描述上述过程中的同步互斥,并说明所用信号量及初值的含义

  答案:

semaphore bowl;
semaphore chopsticks[n];
for( int i=0;i<n;i++ )chopsticks[i]=1;
bowl=min( n-1,m );
Cobegin
while(1)
{思考;P(bowl);P(chopsticks[i]);P(chopsticks[(i+1)%n]);就餐;V(chopsticks[i]);V(chopsticks[(i+1)%n]);V(bowl);
}
Coend

  只要限制最多n-1个哲学家抢筷子,就不会死锁,在这里碗起到了限制哲学家取筷子的作用,成为了解决哲学家问题第1种方法中的r;即当碗<n-1时,最多n-2个碗,最多有n-2个哲学家拿到碗后开始抢筷子,不会死锁,当碗>=n-1时,应让碗的值等于n-1,即最多允许n-1个哲学家拿到碗。因此设置信号量bowl=min(n-1,m);


  4.吸烟者问题

  问题描述:
  假设一个系统有三个抽烟者进程和一个供应者进程。每个抽烟者不停地卷烟并抽掉它,但要卷起并抽掉一支烟,抽烟者需要三种材料:烟草、纸和胶水。三个抽烟者中,第一个拥有烟草,第二个拥有纸,第三个拥有胶水。供应者进程无限地提供三种材料,供应者每次将两种材料放到桌子上,拥有剩下那种材料的抽烟者卷起一根烟并抽掉它,并给供应者一个信号表示已完成,此时供应者会将另外两种材料放到桌上,如此重复(让三个抽烟者轮流地抽烟)
  问题分析:
  (1)关系分析
  供应者与三个抽烟者分别是同步关系。由于供应者无法同时满足两个或以上的抽烟者,三个抽烟者对抽烟这个动作互斥(或由三个抽烟者轮流抽烟得知)
  (2)整理思路
  有四个进程,供应者作为生产者向三个抽烟者提供材料
  (3)信号量设置
  信号量offer1,offer2,offer3分别表示烟草和纸组合的资源、烟草和胶水组合的资源、纸和胶水组合的资源。信号量finish用于互斥进行抽烟动作。

int random;          //存储随机数
semaphore offer1=0;        //定义信号量对应烟草和纸组合的资源
semaphore offer2=0;        //定义信号量对应烟草和胶水组合的资源
semaphore offer3=0;        //定义信号量对应纸和胶水组合的资源
semaphore finish=0;        //定义信号量表示抽烟是否完成
process P1()            //供应者
{while(1){random=任意一个整数随机数;random=random%3;if( random==0 )V(offer1);        //提供烟草和纸else if( random==1 )V(offer2);        //提供烟草和胶水elseV(offer3);     //提供纸和胶水任意两种材料放在桌子上;P(finish);}
}
process P2()            //拥有烟草者
{while(1){P(offer3);拿纸和胶水,卷烟,抽掉;V(finish);}
}
process P3()            //拥有纸者
{while(1){P( offer2 );拿烟草和胶水,卷烟,抽掉;V(finish);}
}
process P4()            //拥有胶水者
{while(1){P( offer1 );拿烟草和纸,卷烟,抽掉;V(finish);}
}

  其他形式的同步问题:
  例题:面包师有很多面包,由n名销售人员推销。每名顾客进店后取一个号,并且等待叫号,当一名销售人员空闲时,就叫下一个号。试设计一个使销售人员和顾客同步的算法
  答案:

int i=0,j=0;
semaphore mutex_i=1,mutex_j=1;
consumer()
{进入面包店;P(mutex_i);取号i;i++;V(mutex_i);等待叫号i并购买面包;
}
seller()
{while(1){P(mutex_j);if( j<=i )         //号j已有顾客取走并等待{叫号j;j++;V(mutex_j);销售面包;}else               //暂时没有顾客在等待{V(mutex_j);休息片刻;}}
}

  注意:(1)顾客和销售人员的同步是借助号码实现的,顾客进店后按序取号,并等待叫号,销售人员按序叫号。(2)使用两个变量i和j分别表示当前的取号值和叫号值,顾客每取一个号,销售每叫一个号,i,j分别加1,对i和j的修改(即取号和叫号)应为互斥的,设mutex_i,mutex_j;(3)若j<=i,则表明还有顾客在等待,否则无顾客等待。(4)顾客进程不需写while(1)

  例题:某博物馆最多可容纳500人同时参观,有一个出入口,该出入口一次仅允许一人通过。参观者的活动描述如下:

cobegin参观者进程i:{...进门;...参观;...出门;...}
coend

  请添加必要的信号量和PV操作,以实现上述过程中的互斥与同步,要求写出完整的过程,说明信号量的含义并赋初值

  答案:

semaphore empty=500;
semaphore mutex=1;
cobegin
参观者进程i:
{...P(empty);P(mutex);进门;V(mutex);参观;P(mutex);出门;V(mutex);V(empty);...
}
coend

  例题:理发店里有一位理发师、一把理发椅和n把供等候理发的顾客坐的椅子。若没有顾客,理发师便在理发椅上睡觉,一位顾客到来时,顾客必须叫醒理发师,若理发师正在理发时又有顾客来到,若有空椅子可坐,则坐下来等待,否则就离开。试用PV操作实现,并说明信号量的定义和初值

  答案:

int waiting=0;          //等候理发的顾客数
int chairs=n;          //为顾客准备的椅子数
semaphore customers=0,barbers=0,mutex=1;
barber()
{while(1)           //理完一人,还有顾客吗{P(customers);   //若无顾客,理发师睡眠P(mutex);        waiting=waiting-1;     //等候顾客数少一个V(barbers);       //理发师去为一个顾客理发V(mutex);Cut_hair();       //正在理发}
}
customer()
{P(mutex);if( waiting<chairs )       //若有空的椅子,就找到椅子坐下等待{waiting=waiting+1;      //等候顾客数加1V(customers);          //唤醒理发师V(mutex);P(barbers);get_haircut;}elseV(mutex);
}

  注意:需设置控制变量waiting表示等待理发的顾客数,之所以使用waiting是因为无法读取信号量的当前值,当waiting<n时,表明可以进店等待,否则离开。对waiting的改变应互斥,设mutex;

  例题:

  答案:

int T2N=1;
int N2T=1;
int L=1;
int K=1;
Proecdure Bike T2N
{P(T2N);P(L);go T to L;go into M;V(L);P(K);go K to N;V(K);V(T2N);
}
Procedure Bike N2T
{P(N2T);P(K);go N to K;go into M;V(K);P(L);go L to T;V(L);V(N2T);
}

  注意:(1)从N到T以及从T到N,每次只能过一辆车,即只有上一辆车完全通过,下一辆车才能开始走。若只对路段K和路段L设信号量,而不进行上述控制,将造成M中车的堆积;(2)由于进行了(1)的控制,任意时刻进入安全岛的车不会超过2辆,因此不必对M设置信号量


2.4 死锁

 2.4.1 死锁的概念

  1.死锁的定义

  所谓死锁,是指多个进程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进
  例如,某计算机系统中只有一台打印机和一台输入设备,进程P1正占用输入设备,同时又提出使用打印机的请求,但此时打印机正在被进程P2所占用,而P2在未释放打印机之前,又提出请求使用正被P1占用的输入设备。这样,两个进程相互无休止地等待下去,均无法继续执行,此时两个进程陷入死锁状态

  2.死锁产生的原因

  (1)系统资源的争夺
  通常系统中拥有的不可剥夺资源,其数量不足以满足多个进程运行的需要,使得进程在运行过程中,会因争夺资源而陷入僵局,如磁带机、打印机等。只有对不可剥夺资源的竞争才可能产生死锁,对可剥夺资源的竞争是不会引起死锁的。
  (2)进程推进顺序非法
  进程在运行过程中,请求和释放资源的顺序不当,也同样会导致死锁。例如,并发进程P1、P2分别保持了资源R1、R2,而进程P1申请资源R2,进程P2申请资源R1时,两者都会因为所需资源被占用而阻塞。
  信号量使用不当也会造成死锁。进程间彼此相互等待对方发来的消息,也会使得这些进程间无法继续向前推进。例如,进程A等待进程B发的消息,进程B又在等待进程A发的消息,可看出进程AB不是因为竞争同一资源,而是在等待对方的资源导致死锁。
  (3)死锁产生的必要条件
  产生死锁必须同时满足以下4个条件,只要其中任意一个条件不成立,死锁就不会发生。
  互斥条件:进程要求对所分配的资源(如打印机)进行排它性控制,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待
  不剥夺条件:进程所获得的资源在未使用完之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放(只能是主动释放)
  请求并保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
  循环等待条件:存在一种进程资源的循环等待链,链中每个进程已获得的资源同时被链中下一个进程所请求。即存在一个处于等待态的进程集合{P1,P2,…,Pn},其中Pi等待的资源被Pi+1(i=0,1,…,n-1)占有,Pn等待的资源被P0占有

  循环等待条件和死锁的定义有区别。按死锁定义构成的等待环所要求的条件更严,它要求Pi等待的资源必须由Pi+1来满足,而循环等待条件则无此限制。例如,系统中有两台输出设备,P0占有一台,PK占有一台,且K不属于集合{0,1,…,n}。Pn等待一台输出设备,它可以从P0获得,也可能从PK获得。因此,虽然Pn,P0和其他一些进程形成了循环等待圈,但PK不在圈内,若PK释放了输出设备,则可打破循环等待。因此循环等待只是死锁的必要条件。

  资源分配图含圈而系统又不一定有死锁的原因是,同类资源数大于1。但若系统中每类资源都只有一个资源,则资源分配图含圈就变成了系统出现死锁的充分必要条件。


  死锁与饥饿:
  一组进程处于死锁状态是指组内的每个进程都在等待一个事件,而该事件只可能由组内的另一个进程产生。这里所关心的主要是资源的获取和释放
  无限期阻塞或饥饿,即进程在信号量内无穷等待的情况。
  产生饥饿的主要原因是:在一个动态系统中,对于每类系统资源,OS需要确定一个分配策略,当多个进程同时申请某类资源时,由分配策略确定资源分配给进程的次序。有时资源分配策略可能是不公平的,即不能保证等待时间上界的存在。在这种情况下,即使系统没有发生死锁,某些进程也可能会长时间等待。当等待时间给进程推进和响应带来明显影响时,称发生了进程饥饿,当饥饿到一定程度的进程所赋予的任务即使完成也不再具有实际意义时,称该进程被饿死。
  饥饿并不表示系统一定会死锁,但至少有一个进程的执行被无限期推迟。饥饿与死锁的主要差别如下:
  (1)进入饥饿状态的进程可以只有1个,而因循环等待条件进入死锁状态的进程必须大于等于2个
  (2)处于饥饿状态的进程可以是一个就绪进程,如静态优先权调度算法时的低优先权进程,而处于死锁状态的进程必定是阻塞进程


 2.4.2 死锁的处理策略

  为使系统不发生死锁,必须设法破坏产生死锁的4个必要条件之一,或允许死锁产生,但当死锁发生时能检测出死锁,并有能力实现恢复

  1.死锁预防

  设置某些限制条件,破坏产生死锁的4个必要条件中的一个或几个,以防止发生死锁

  2.避免死锁

  在资源的动态分配过程中,用某种方法防止系统进入不安全状态,从而避免死锁

  3.死锁的检测及解除

  无须采取任何限制性措施,允许进程在运行过程中发生死锁。通过系统的检测机构及时地检测出死锁的发生,然后采取某种措施解除死锁

  预防死锁和避免死锁都属于事先预防策略,预防死锁的限制条件比较严格, 实现起来较为简单,但往往导致系统的效率低,资源利用率低;避免死锁的限制条件相对宽松, 资源分配后需要通过算法来判断是否进入不安全状态,实现起来较为复杂。
  死锁的几种处理策略的比较如下:

 2.4.3 死锁预防

  防止死锁的发生只需破坏死锁产生的4个必要条件之一即可

  1.破坏互斥条件

  若允许系统资源都能共享使用,则系统不会进入死锁状态。但有些资源根本不能同时访问,如打印机等临界资源只能互斥使用。所以,破坏互斥条件而预防死锁的方法不太可行, 而且在有的场合应该保护这种互斥性。

  2.破坏不剥夺条件

  当一个已保持了某些不可剥夺资源的进程请求新的资源而得不到满足时,它必须释放已经保持的所有资源,待以后需要时再重新申请。这意味着,一个进程已占有的资源会被暂时释放,或者说是被剥夺,或从而破坏了不剥夺条件。
  该策略实现起来比较复杂,释放已获得的资源可能造成前一阶段工作的失效,反复地申请和释放资源会增加系统开销,降低系统吞吐量。这种方法常用于状态易于保存和恢复的资源,如CPU的寄存器及内存资源,一般不能用于打印机之类的资源。

  3.破坏请求并保持条件

  采用预先静态分配方法,即进程在运行前一次申请完它所需要的全部资源,在它的资源未满足前,不把它投入运行。一旦投入运行,这些资源就一直归它所有,不再提出其他资源请求,这样就可以保证系统不会发生死锁。
  使用这种方式,系统资源被严重浪费,其中有些资源可能仅在运行初期或运行快结束时才使用,甚至根本不使用。而且还会导致饥饿现象,由于个别资源长期被其他进程占用时,将致使等待该资源的进程迟迟不能开始运行。

  4.破坏循环等待条件

  为了破坏循环等待条件,可采用顺序资源分配法。 首先给系统中的资源编号,规定每个进程必须按编号递增的顺序请求资源,同类资源一次申请完。也就是说,只要进程提出申请分配资源Ri,则该进程在以后的资源申请中就只能申请编号大于Ri的资源
  这种方法的问题是,编号必须相对稳定,这就限制了新类型设备的增加;尽管在为资源编号时已考虑到大多数作业实际使用这些资源的顺序,但也经常会发生作业使用资源的顺序与系统规定顺序不同的情况,造成资源的浪费;此外,这种按规定次序申请资源的方法,也必然会给用户的编程带来麻烦

 2.4.4 死锁避免

  避免死锁同样属于事先预防策略,但并不是事先采取某种限制措施破坏死锁的必要条件,而是在资源动态分配过程中,防止系统进入不安全状态,以避免发生死锁。 这种方法所施加的限制条件较弱,可以获得较好的系统性能

  1.系统安全状态

  避免死锁的方法中,允许进程动态地申请资源,但系统在进行资源分配之前,应先计算此次分配的安全性。若此次分配不会导致系统进入不安全状态,则允许分配;否则让进程等待。
  所谓安全状态,是指系统能按某种进程推进顺序(P1,P2,…,Pn)为每个进程Pi分配其所需的资源,直至满足每个进程对资源的最大需求,使每个进程都可顺序完成。此时称P1,P2,…,Pn为安全序列。若系统无法找到一个安全序列,则称系统处于不安全状态。
  假设系统中有三个进程P1,P2和P3,共有12台磁带机。进程P1共需要10台磁带机,P2和P3分别需要4台和9台。假设在T0时刻,进程P1,P2和P3已分别获得5台、2台和2台,尚有3台未分配,见下表:

  在T0时刻是安全的,因为存在一个安全序列P2,P1,P3,只要系统按此进程序列分配资源,那么每个进程都能顺利完成。也就是说,当前可用磁带机为3台,先把3台磁带机分配给P2以满足最大需求,P2结束并归还资源后,系统有5台磁带机可用;接下来给P1分配5台磁带机以满足其最大需求,P1结束并归还资源后,剩余10台磁带机可用;最后分配7台磁带机给P3,这样P3也能顺利完成
  若在T0时刻后,系统分配1台磁带机给P3,系统剩余可用资源数为2,此时系统进入不安全状态,因为此时已无法再找到一个安全序列。当系统进入不安全状态后,便可能导致死锁。例如,把剩下的2台磁带机分配给P2,这样,P2完成后只能释放4台磁带机,既不能满足P1又不能满足P3,致使它们都无法推进到完成,彼此都在等待对方释放资源,陷入僵局,导致死锁。

  并非所有的不安全状态都是死锁状态,但当系统进入不安全状态后,便可能进入死锁状态;反之,只要系统处于安全状态,系统便可避免进入死锁状态。

  2.银行家算法

  银行家算法的思想是:把OS视为银行家,OS管理的资源相当于银行家管理的资金,进程向OS请求分配资源相当于用户向银行家贷款。OS按照银行家制定的规则为进程分配资源。进程运行之前先声明对各种资源的最大需求量,当进程在执行中继续申请资源时,先测试该进程已占用的资源数与本次申请的资源数之和是否超过该进程声明的最大需求量。若超过则拒绝分配资源,若未超过则再测试系统现存的资源能否满足该进程尚需的最大资源量,若能满足则按当前的申请量分配资源,否则也要推迟分配。
  (1)数据结构描述
  可利用资源向量Available:
  含有m个元素的数组,其中每个元素代表一类可用的资源数目。Available[j]=K表示系统中现有Rj类资源K个
  最大需求矩阵Max:
  nxm矩阵,定义系统中n个进程中的每个进程对m类资源的最大需求。简单来说,一行代表一个进程,一列代表一类资源。Max[i,j]=K表示进程i需要Rj类资源的最大数目为K
  分配矩阵Allocation:
  nxm矩阵,定义系统中每类资源当前已分配给每个进程的资源数。Allocation[i,j]=K表示进程i当前已分得Rj类资源的数目为K。
  需求矩阵Need:
  nxm矩阵,表示每个进程接下来最多还需多少资源。Need[i,j]=K表示进程i还需Rj类资源的数目为K

  上述三个矩阵间存在下列关系:
  Need=Max-Allocation

  (2)银行家算法描述
  设Requesti是进程Pi的请求向量,Requesti[j]=K表示进程Pi需要j类资源K个。当Pi发出资源请求后,系统按下列步骤进行检查:
  a.若Requesti[j]<=Need[i,j],则转向步骤b;否则认为出错,因为它所需要的资源数已超过它所宣布的最大值
  b.若Requesti[j]<=Available[j],则转向步骤c;否则,表示尚无足够资源,Pi须等待
  c.系统试探着把资源分配给进程Pi,并修改下面数据结构中的数值:
  Available=Available-Requesti
  Allocation[i,j]=Allocation[i,j]+Requesti[j]
  Need[i,j]=Need[i,j]-Requesti[j]
  d.系统执行安全性算法,检查此次资源分配后,系统是否处于安全状态。若安全,才正式将资源分配给进程Pi,以完成本次分配;否则,将本次的试探分配作废,恢复原来的资源分配状态,让进程Pi等待。

  (3)安全性算法
  设置工作向量Work,有m个元素,表示系统中的剩余可用资源数目。在执行安全性算法开始时,Work=Available。
  a.初始时安全序列为空
  b.从Need矩阵中找出符合下面条件的行:该行对应的进程不在安全序列中,而且该行小于等于Work向量,找到后,把对应的进程加入安全序列;若找不到,则执行步骤d
  c.进程Pi进入安全序列后,可顺利执行,直至完成,并释放分配给它的资源,因此应执行Work=Work+Allocation[i],其中Allocation[i]表示进程Pi代表的在Allocation矩阵中对应的行,返回步骤b
  d.若此时安全序列中已有所有进程, 则系统处于安全状态,否则系统处于不安全状态

  3.安全性算法举例

  假定系统中有5个进程{P0,P1,P2,P3,P4}和三类资源{A,B,C},各种资源的数量分别为10,5,7,在T0时刻的资源分配情况如下表:

  4.银行家算法举例

  安全性算法是银行家算法的核心
  假设当前系统中资源的分配和剩余情况如表所示:

 2.4.5 死锁检测和解除

  前面介绍的死锁预防和避免算法,都是在为进程分配资源时施加限制条件或进行检测,若系统为进程分配资源时不采取任何措施,则应该提供死锁检测和解除的手段

  1.资源分配图

  系统死锁可利用资源分配图来描述。用圆圈代表一个进程,用框代表一类资源。由于一种类型的资源可能有多个,因此用框中的一个圆代表一类资源中的一个资源。 从进程到资源的有向边称为请求边,表示该进程申请一个单位的该类资源;从资源到进程的边称为分配边,表示该类资源已有一个资源分配给了该进程。

  在上图所示的资源分配图中,进程P1已经分得了两个R1资源,并又请求一个R2资源;进程P2分得了一个R1资源和一个R2资源,并又请求一个R1资源

  2.死锁定理

  简化资源分配图可检测系统状态S是否为死锁状态。 简化方法如下:
  (1)在资源分配图中,找出既不阻塞又不孤点的进程Pi(即找出一条有向边与它相连,且该有向边对应资源的申请数量小于等于系统中已有的空闲资源数量,若所有连接该进程的边均满足上述条件,则这个进程能继续运行直至完成,然后释放它所占有的所有资源)。消去它所有的请求边和分配边,使之成为孤立的结点。
  判断某种资源是否有空闲,应用它的资源数量减去它在资源分配图中的出度。
  (2)进程Pi所释放的资源,可以唤醒某些因等待这些资源而阻塞的进程,原来的阻塞进程可能变为非阻塞进程。根据(1)中的方法进行一系列简化后,若能消去图中所有的边,则称该图是可以完全简化的。

  S为死锁的条件是当且仅当S状态的资源分配图是不可完全简化的,该条件为死锁定理。

  3.死锁解除

  一旦检测出死锁,就应采取相应的措施来解除死锁。死锁解除的主要方法有:
  (1)资源剥夺法
  挂起某些死锁进程,并抢占它的资源,将这些资源分配给其他的死锁进程。但应防止被挂起的进程长时间得不到资源而处于资源匮乏的状态
  (2)撤销进程法
  强制撤销部分甚至全部死锁进程并剥夺这些进程的资源。撤销的原则可以按进程优先级和撤销进程代价的高低进行
  (3)进程回退法
  让一(或多)个进程回退到足以回避死锁的地步,进程回退时自愿释放资源而非被剥夺。要求系统保持进程的历史信息,设置还原点


  例题:解除死锁通常不采用的方法是()
  A.终止一个死锁进程  B.终止所有死锁进程
  C.从死锁进程处抢夺资源  D.从非死锁进程处抢夺资源

  答案:D;

  例题:在下列死锁的解决方法中,属于死锁预防策略的是()
  A.银行家算法  B.资源有序分配算法
  C.死锁检测算法  D.资源分配图化简法

  答案:B;

  例题:三个进程共享四个同类资源,这些资源的分配与释放只能一次一个。已知每个进程最多需要两个该类资源,则该系统()
  A.有些进程可能永远得不到该类资源
  B.必然有死锁
  C.进程请求该类资源必然能得到
  D.必然是死锁

  答案:C;不会出现死锁

  例题:
  答案:C;

  例题:下列关于银行家算法的叙述中,正确的是()
  A.银行家算法可以预防死锁
  B.当系统处于安全状态时,系统中一定会出现死锁进程
  C.当系统处于不安全状态时,系统中一定会出现死锁进程
  D.银行家算法破坏了死锁必要条件中的请求和保持条件

  答案:B;

  例题:某系统有n台互斥使用的同类设备,三个并发进程分别需要3,4,5台设备,可确保系统不发生死锁的设备数n最小是
  答案:10

  例题:若系统S1采用死锁避免方法,S2采用死锁检测方法。下列叙述中,正确的是()
  1.S1会限制用户申请资源的顺序,而S2不会
  2.S1需要进程运行所需的资源总量信息,而S2不需要
  3.S1不会给可能导致死锁的进程分配资源,而S2会

  答案:2,3;限制用户申请资源的顺序属于死锁预防的范畴

  例题:设系统中有下述解决死锁的方法:
  (1)银行家算法
  (2)检测死锁,终止处于死锁状态的进程,释放该进程占有的资源
  (3)资源预分配
  简述哪种办法允许最大的并发性,即哪种办法允许更多的进程无等待地向前推进。请按并发性从大到小对上述三种办法排序

  答案:死锁检测方法>银行家算法>资源预分配法

  例题:某银行计算机系统要实现一个电子转账系统,基本业务流程是:首先对转出方和转入方的账户进行加锁,然后进行转账业务,最后对转出方和转入方的账户进行解锁。若不采取任何措施,系统会不会发生死锁?为什么?请设计一个能够避免死锁的办法

  答案:会。因为对两个账户进行加锁操作是可以分割进行的,若此时有两个用户同时进行转账,P1先对账户A进行加锁,再申请账户B;P2先对账户B进行加锁,再申请账户A,此时产生死锁。解决的办法是:可以采用资源顺序分配法对A、B账户进行编号,用户转账时只能按照编号由小到大进行加锁;也可采用资源预分配法,要求用户在使用资源前将所有资源一次性申请到

  例题:
  答案:这段程序在不同的运行推进速度下,可能会产生死锁。例如,进程P1先申请资源R1,得到资源R1,然后进程R2申请资源R2,得到资源R2,进程P1又申请资源R2,因资源R2已分配, 使得进程P1阻塞。进程P1和进程P2都因申请不到资源而形成死锁。若改变进程的运行顺序,则这两个进程就不会出现死锁现象。
  产生死锁现象的原因可归结为两点:
  (1)竞争资源
  (2)进程推进顺序非法
  产生死锁的必要条件:
  (1)互斥条件
  (2)请求并保持条件
  (3)不剥夺条件
  (4)环路等待条件

  例题:

  答案:在本题中,不发生死锁要求必须保证至少有一个进程能得到所需的全部资源并执行完毕,m>=n(k-1)+1时,一定不会发生死锁

  例题:有三个进程P1,P2,P3并发工作。进程P1需要资源S3和资源S1; 进程P2需要资源S2和资源S1;进程P3需要资源S3和资源S2
  (1)若对资源分配不加限制,会发生什么情况,为什么
  (2)为保证进程正确运行,应采用怎样的分配策略?列出所有可能的方法

  答案:(1)可能发生死锁。例如,P1占有S1申请S3,P2占有S2申请S1,P3占有S3申请S2
  (2)A:采用静态分配:由于执行前已获得所需的全部资源,因此不会出现占有资源又等待别的资源的现象
  B:采用按序分配:不会出现循环等待资源的现象
  C:采用银行家算法:在分配时,保证系统处于安全状态

  例题:
  答案:(1)

  (2)给P1试分配资源,系统进入不安全状态,因此不能将资源分配给P1;给P2试分配资源,存在安全序列,因此能将资源分配给P2
  (3)若(2)中的两个请求立即得到满足,则此刻系统并未立即进入死锁状态,因为这时所有的进程未提出新的资源申请,全部进程均未因资源请求没有得到满足而进入阻塞态。只有当进程提出资源申请且全部进程都进入阻塞态时,系统才处于死锁状态

OS知识点汇总(考研用)——第二章:进程管理(下)相关推荐

  1. (王道408考研操作系统)第二章进程管理-第三节10:经典同步问题之哲学家进餐问题

    本文接: (王道408考研操作系统)第二章进程管理-第三节6:经典同步问题之生产者与消费者问题 ((王道408考研操作系统)第二章进程管理-第三节7:经典同步问题之多生产者与多消费者问题 (王道408 ...

  2. (王道408考研操作系统)第二章进程管理-第三节8:经典同步问题之吸烟者问题

    本文接: (王道408考研操作系统)第二章进程管理-第三节6:经典同步问题之生产者与消费者问题 ((王道408考研操作系统)第二章进程管理-第三节7:经典同步问题之多生产者与多消费者问题 文章目录 一 ...

  3. (王道408考研操作系统)第二章进程管理-第三节7:经典同步问题之多生产者与多消费者问题

    注意:生产者与消费者问题Linux系统编程专栏有案例讲解 Linux系统编程39:多线程之基于阻塞队列生产者与消费者模型 Linux系统编程40:多线程之基于环形队列的生产者与消费者模型 本文接:(王 ...

  4. (王道408考研操作系统)第二章进程管理-第一节3:进程控制(配合Linux讲解)

    文章目录 一:如何实现进程控制 二:进程控制原语 (1)进程创建 A:概述 B:补充-Linux中的创建进程操作 ①:fork() ②:fork()相关问题 (2)进程终止 A:概述 B:补充-僵尸进 ...

  5. (王道408考研操作系统)第二章进程管理-第二节4:调度算法详解2(RR、HPF和MFQ)

    文章目录 一:时间片轮转调度算法(RR) 二:优先级调度算法(HPF) 三:多级反馈队列调度算法(MFQ) 总结 进程调度算法也称为CPU调度算法,操作系统内存在着多种调度算法,有的调度算法适用于作业 ...

  6. (王道408考研操作系统)第二章进程管理-第一节1:进程、PCB及其特征

    注意: 本章内容涉及进程,测试环境选在Linux下,因为我们可以阅读其源代码,有助于理解 Linux中的命令不会涉及太专业,需要说明的地方都会说明,读者只需关注其功能,无需过于操心原理 有兴趣的读者可 ...

  7. (王道408考研操作系统)第二章进程管理-第二节6、7:调度算法详解2(RR、HPF和MFQ)

    文章目录 一:时间片轮转调度算法(RR) 二:优先级调度算法(HPF) 三:多级反馈队列调度算法(MFQ) 总结 进程调度算法也称为CPU调度算法,操作系统内存在着多种调度算法,有的调度算法适用于作业 ...

  8. (王道408考研操作系统)第二章进程管理-第四节3:死锁处理策略之检测和解除

    文章目录 一:死锁检测算法 (1)资源分配图 (2)死锁定理 二:死锁解除算法 如果系统中既不采取预防死锁的措施,也不采取避免死锁的措施,系统就极有可能发生死锁,在这种情况下系统应当提供两个算法: 死 ...

  9. (王道408考研操作系统)第二章进程管理-第四节2:死锁处理策略之避免死锁(银行家算法)

    文章目录 一:安全状态.安全序列 二:银行家算法 (1)数据结构描述 (2)银行家算法描述 (3)安全性算法 一:安全状态.安全序列 避免死锁:允许进程动态申请资源,但系统在进行资源分配的时候,应该先 ...

  10. (王道408考研操作系统)第二章进程管理-第四节1:死锁相关概念

    一:死锁的基本概念 (1)死锁定义 死锁:所谓死锁,是指多个进程因竞争资源而造成的一种互相等待的局面,若无外力作用,这些进程将无法向前推进 生活中死锁的例子: 1:我拿了你房间的钥匙,而我在自己的房间 ...

最新文章

  1. 网页加载出现没有合适的负载均衡器_终于讲清楚了,什么是负载均衡(Load balancing)...
  2. Matplotlib实例教程(九)热力图
  3. c语言位运算_C语言自增减、逻辑运算、位运算、位移运算及三目运算操作
  4. 走进异步世界:博客程序的异步化改造以及发布后的不理想情况
  5. 对KVC和KVO的理解
  6. 绑定dictionary 给定关键字不再字典中_对字典嵌套的理解及二级下拉菜单的制作...
  7. 清华大学2017届本科毕业典礼演讲——做有思想的行者
  8. 写出一手烂代码的 19 条准则!
  9. webpack@3.6.0(4) -- 配置模块化开发
  10. 加载远程图片_Cocos Creator工程JavaScript实现远程图片的加载
  11. python win32api键盘_Python win32api.keybd_event模拟键盘输入
  12. python的string与Unicode转换,gbk字符串编码
  13. 基于深度学习的SLAM综述:迈向空间机器智能时代
  14. WPS按Tab键无效果
  15. CUDA开源 轻松跑AMD GPU/Intel x86 CPU
  16. 翟菜花:资本退烧、开源节流时代,品质服务保障成携程终极利器
  17. rasterio实用教程(4)——坐标系转换
  18. 七种常见的数据分析法之:可行域分析
  19. 又闹分裂?Node.js 被分叉出一个项目 — Ayo.js
  20. matlab实现大气湍流退化模型算法,matlab与图像处理

热门文章

  1. LZY的计算器(暴力)
  2. 全面理解Gradle - 定义Task
  3. windows平台基础工作软件整理【持续更新】
  4. 无线华为能连苹果不能连接到服务器,华为网络正常app连不上网络
  5. 【Markdown编辑器实用技巧】
  6. 【Android App】人脸识别中扫描识别二维码实战解析(附源码和演示 超详细)
  7. 解决IndexError: Target 2 is out of bounds.
  8. Kali Linux 安装搜狗拼音输入法
  9. 计算机机房网络维护日志,机房网络设备维护方案.doc
  10. 惠普打印机卡纸问题,解决.