操作系统之信号量机制

目录

整型信号量

记录型信号量

利用信号量实现同步

利用信号量实现进程互斥

利用信号量实现前驱关系

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


信号量机制可用来解决互斥与同步问题,它只能被两个标准原语(执行过程中不可以被中断,通常可由硬件来实现)wait(S)signal(S)访问,也可以记作“P操作(wait)”和“V操作(signal)”。

原语的操作类似TestAndSet,Swap和关中断在我的另一篇文章有写到《操作系统之进程同步问题》中有叙述到。

原语之所以不能被中断,是因为原语对变量的操作过程如果被打断,可能会去允许另一个对同一变量的操作过程,从而出现临界段问题。若能够找到一种解决临界段问题的元方法(原语),就可以实现对共享变量操作的原子性。

信号量其实就是一个变量(可以是整型,也可以是更为复杂的记录型,一般如果未提到具体类型,那么就是记录型),可以用一个信号量来表示系统中某些资源的数量。例如,我有一台打印机,那么可以设置初始值为1的信号量。

整型信号量

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

  1. wait(S){  

  2.   while(S<=0);  

  3.   S=S-1;  

  4. }  

  5. signal(S){  

  6.   S=S+1;  

  7. }  

wait原语,相当于“进入区”,如果资源数不够,比如我打印机本来只有1台,然后我使用wait之后,发现我的S是1,那么可以执行打印机,并且执行S=S-1,那打印机被使用,我想再次使用的时候发现S是0,满足while语句,那么就会循环等待。如果资源数够,则占用资源,就像刚才打印机够,我可以占用打印机了一样。

siganl原语,相当于“退出区”,使用完资源后,在退出区释放资源。和wait的例子一样,刚才我占用了打印机,那么我现在使用完打印机了,我用让S+1,告诉它打印机可以使用了。

以上对信号量的操作只有三种,初始化S,P操作(wait)、V操作(signal)。

Wait操作中,只要信号量一直满足S≤0,就会不断测试。因此,没有遵循“让权等待”的准则,而是使进程处于“忙等”。 PS:让权等待在进程同步问题一文有解释。

记录型信号量

记录型的信号量就不存在上述“忙等”的现象。除需要一个用于代表资源数目的整型变量value之外,再增加一个进程链表L,用于存放所有等待该资源的进程。记录型信号量得名于采用记录型数据结构。可以描述为

  1. typedef struct{  

  2.   int value;//剩余资源数  

  3.   struct process *L;//等待队列  

  4.   

  5. }  

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

  1. void wait(semaphore S){//相当于申请资源  

  2.   S.value--;  

  3.   if(S.value<0){  

  4.     add this process to S.L;  

  5.     block(S.L);  

  6.   }  

  7. }  

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

  1. void signal(semaphore S){//相等于释放资源  

  2.   S.value++;  

  3.   if(S.value<=0){  

  4.     remove a process P from S.L;  

  5.     wakeup(P);  

  6.   }  

  7. }  

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

举个例子,假如我现在有一台打印机,那么我的S.value就是1,现在我有一个进程进来了,他使用了打印机资源,那么S.value进行减1,变成了0,但是我S.L并不用阻塞,因为没有需要等待的进程。现在我又来了个进程,我使用打印机,S.value就变成了-1,经过判断语句发现S.value是小于0的,所以把该进程加入到阻塞队列S.L,并且进行阻塞。再来一个进程S.value就变成了-2,阻塞队列S.L里就有两个进程,其实就是S.value负数的绝对值。如果S.L里就一个进程,那么第一个进来的使用完打印机了,它用signal释放了资源,我们发现S.value就变成了0,这时候资源是可用的,那么就从阻塞队列里拿出进程,并且唤醒它。

利用信号量实现同步

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

可以使用两个步骤去完成

  1. 分析什么地方需要实现“同步关系”,即必须保证“一前一后”执行的两种操作

  2. 设置同步信号量S,初始为0

其实现进程同步的算法如下:

  1. semaphore S=0; //初始化信号量  

  2. P1(){  

  3.  x; //语句x  

  4.  V(S);//告诉进程P2,语句x已经完成  

  5.  ..  

  6. }  

  7. P2(){  

  8.  ..  

  9.  P(S); //检查语句x是否运行完成  

  10.  y;//检查无误,运行y语句  

  11.  ...  

  12. }  

前V后P,在“前操作”执行后V,“后操作”前P。

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

利用信号量实现进程互斥

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

跟同步一样,同样可以用两个步骤分析

  1. 分析并发进程的关键活动,划定临界区。

  2. 设置互斥信号量mutex,初值为1(理解为“进入临界区的名额”),对不同的临界资源需要设置不同的互斥信号量

算法如下。

  1. semaphore mutex=1; //初始化信号量  

  2. P1(){  

  3.   ...  

  4.   P(S);//准备开始访问临界资源,加锁  

  5.   进程P1的临界区;  

  6.   V(S);//访问结束,解锁  

  7.   ...  

  8. }  

  9. P2(){  

  10.   ...  

  11.   P(S);//准备开始访问临界资源,加锁  

  12.   进程P2的临界区;  

  13.   V(S);//访问结束,解锁  

  14.   ...  

当一个进程进入时,执行P操作,S就变为0,进入临界区。当有进程在临界区时,S为0,再有进程要进入临界区,执行P操作时就会被阻塞,直到临界区内没有资源。

互斥中,P、V操作必须成对出现。缺少P不能保证互斥,缺少V不能唤醒。

利用信号量实现前驱关系

 

如上图,S2的前提是S1,S3的前提也是S1,S6的前提是S4和S5。图中信号量已给出。

实现的算法如下:

  1. semaphore a=b=c=d=e=f=g=0;//初始化信号量  

  2. S1(){  

  3.   ...;  

  4.   V(a);V(b);//表示S1已经完成释放a和b两个线(这里这个线只是好说明)  

  5. }  

  6. S2(){  

  7.   P(a);//检查S1是否运行完成  

  8.   ...;  

  9.   V(c);V(d);//释放c和d两条线的信号量  

  10. }  

  11. S3(){  

  12.   P(b);//检查S1是否完成  

  13.   ...;  

  14.   V(e);//释放e这条信号量  

  15. }  

  16. S4(){  

  17.   P(c);//检查S2是否完成  

  18.   ...;  

  19.   V(f);//释放f这条信号量  

  20. }  

  21. S5(){  

  22.   P(d);//检查S2是否已经运行完成  

  23.   ...;  

  24.   V(g);//释放g这条信号量  

  25. }  

  26. S6(){  

  27.   P(e);//检查S3是否完成  

  28.   P(f);//检查S4是否完成  

  29.   P(g);//检查S5是否完成  

  30. }  

总的来说看图就知道,哪个是哪个的前面,那我某个要使用,我就一定要检查它的直接前驱有没有完成,每当完成某个的时候,我要释放的是与它相关联的后继。

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

其实方法和步骤在上面都有写到,这里单独再拎出来。

  1. 关系分析。找出问题中的进程数,并分析它们之间的同步和互斥关系。

  2. 整理思路。找出解决问题的关键点,并根据上面的例子找出求解思路。确定P和V的大致顺序。

  3. 设置信号量。根据上面的两步,设置需要的信号量,确定初值,完善整理。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值