信号量学习笔记

来源:互联网 发布:java中for循环 编辑:程序博客网 时间:2024/06/05 17:32
为了进程间通信的竞争条件,必须阻止多个进程同时读写共享的数据。Peterson解法,TSL(test and set lock)指令和XCHG(X86 CPU中跟TSL等价的指令)能够正确的防止多个进程同时读写共享数据,但是这三个解法都有一个缺点,那就是忙等待。
     忙等待显然非常的浪费CPU时间,但是比浪费CPU时间更严重的问题是忙等待会导致优先级反转问题(Priority inversion problem)。所谓优先级反转问题,就是当低优先级的进程占用了跟高优先级进程共享的资源,此时高优先级进程就绪了,根据调度规则只要高优先级出于就绪就可以运行它。但是低优先级进程占用了共享资源,于是高优先级进程开始忙等待,而低优先级进程得不到调度无法走出临界区释放共享资源,结果就导致高优先级进程一直忙等待。
     为了避免忙等待带来的优先级反转问题,有个办法,那就是当进程无法进入临界区获取共享资源时,不是忙等待,而是被阻塞,既sleep和wakeup。       
     那么sleep和wakeup是否能很好的解决问题或者没有弊端?先看经典的生产者消费者问题:

  

#define N 100           /*缓冲区中的槽数目*/int count = 0;            /*缓冲区中的数据项数目*/void producer(void){      int item;      while(TRUE)      {        item=produce_item();        if(count==N) sleep();        insert_item(item);        count=count+1;        if(count==1) wakeup(consumer);    /*count==1说明生产之前count==0,consumer进程应该在睡眠,遂唤醒它*/      }}void consumer(void){     int item;     while(TRUE)      {        if(count==0) sleep();        item=remove_item();        count=count-1;        if(count==N-1) wakeup(producer);  /*count==N-1说明消费之前缓冲区满了,生产者应该在睡眠,遂唤醒*/        consume_item(item);      }}

      这里有可能会出现竞争条件,因为对count的方位未加限制。有可能出现以下情况:缓冲区为空,消费者刚刚读取count的值发现它为0.此时调度程序把 CPU切换给了生产者。生产者想缓冲区中加入一个数据项,count加1,并且它推断由于刚才count为0,所以消费者一定在睡眠,所以调用 wakeup来唤醒消费者。但消费者并未睡眠,所以wakeup信号丢失。当消费者下次运行时,它将测试先前读到的count值,发现为0,于是睡眠。生 产者迟早会填满整个缓冲区,然后睡眠。最后两个进程都将永远睡眠下去。
     问题的实质在于发给一个尚未睡眠的进程的wakeup信号丢失了。好了,接下来信号量登场。
     信号量(semaphore)是一个用来累计唤醒次数的整型变量,一个信号量的取值可以为0或正值。信号量有down和up两种操作。
     down操作是指检查其值是否大于0,若大于0,将其减1并继续;若该值为0,进程睡眠,down操作结束。特别要注意的是,信号量是一种新的变量类型,它检查数值、修改变量值以及可能发生的睡眠操作均为一个单一的、不可分割的原子操作。
    up操作是指对信号量增1.如果在对一个信号量做up之前,信号量值为0,并且有一个或多个进程想对该信号量做down操作,但因为信号量值为0导致进程睡眠,无法完成down操作,此时另外一个进程对该信号量做up操作,那么系统将选择其中一个睡眠进程允许其完成down操作,但是信号量值仍然为0,只不过在该信号量上睡眠的进程少了一个,因为让它完成了down操作。
     用信号量解决了丢失的wakeup问题。为确保信号量能正确工作,最重要的是要采用一种不可分割的方式来实现它。通常是将up和down作为系统调用实现,而且系统只需要在执行以下操作使暂时屏蔽全部中断:测试信号量、更新信号量以及在需要时使某个进程睡眠。由于这些动作只需要几条指令,所以屏蔽中断不会带来什么副作用。如果使用多个CPU,则每一个信号量应该由一个锁变量进行保护,通过TSL或XCHG指令来确保同一时刻只有一个CPU在对信号量进行操作。
     应该注意的是,使用TSL或XCHG忙等待另一个CPU对一个信号量的操作与使用TSL或XCHG忙等待另一个进程离开临界区是完全不同的,因为信号量的操作就是几条指令而已,时间是固定的几个毫秒,而等待另一个进程离开缓冲区的时间则是未知的,可能任意长。
    下面是一个用信号量实现的生产者消费者代码:

#define N 100           /*缓冲区中的槽数目*/typedef int semaphore;      /*信号量是一种特殊的整型数据*/semaphore mutex = 1;       /*计数缓冲区的空槽数目*/semaphore empty = N;      /*计数缓冲区的满槽数目*/void producer(void){      int item;      while(TRUE)      {        item=produce_item();        down(&empty);     /*空槽数目减一*/        down(&mutex);     /*进入临界区*/        insert_item(item);        up(&mutex);        /*离开临界区*/        up(&full);            /*将满槽的数目加1*/      }}void consumer(void){     int item;     while(TRUE)      {        down(&full);        down(&mutex);        item=remove_item();        up(&mutex);        up(&empty);        consume_item(item);      }}



0 0