一、信号量简单介绍
最近学习了下操作系统中的信号量的概念,忽然发现和GCD中的信号量的概念几乎一样,估计GCD的dispatch_semaphore也是根据此封装的,在这里就简单的介绍下它的概念。
信号量是由荷兰科学家E.W.Dijkstra在1965年提出的要求信号量是一个整数,除了初始化之外,它就只能去通过wait和signal去访问,这其实和我们在ios中的dispatch_semaphore_create和dispatch_semaphore_wait以及dispatch_semaphore_signal是相对应的
在wait和signal操作中,对信号量的整形值的修改必须不可分的去执行也就是说必须不能中断的执行也就是原子性的执行,不能有其他进程同时其修改同一个信号量,因为是这样子的,比如说我wait(S)的内部操作和signal(S)的内部操作是这样的(这里用S来表示信号量)
Wait()的定义
wait(S){
while(S<=0);
S--;
}
signal()的定义
signal(S){
S++;
}
我们可以看出来,如果说进程可以同时去执行修改同一个信号量的值,那么比如说我两个进程都执行到了while(S<=0); 这个的下面,也就是说都去执行了S–,如果说我S值原本是1并且S值规定的是必须要大于等于0的,那么这样肯定是会出问题的。
一般来说操作系统是会去区分计数信号量和二进制信号量的,计数信号量的值域是不受限制的,而如果是二进制信号量的话其实就只能是0或者是1.我们就可以通过二进制信号量来达到互斥的效果,所以有些操作系统就把二进制信号量称为是互斥锁。
计数信号量的话可以用来控制访问某个资源的个数,比如说我目前有5个资源可以访问,当访问完这五个资源之后,如果还有其他进程想要访问的话,那么就需要等前面那5个资源有被释放的,否则的话需要使用资源的进程就会被阻塞。
信号量可以解决一些同步问题
比如说有两个并发进程:A进程有语句S1,想先执行,然后再让B进程的S2语句去执行,这样的话我们利用信号量的概念能很好的解决这个问题,只需要让A和B进程共享一个信号量semaphore就可以了,我们把semaphore初始化为0。然后分别在进程A和进程B插入语句
进程A
S1;
signal(semaphore);
进程B
wait(semaphore);
S2;
根据我们前面的定义,我们应该能了解到我们定义的前面的信号量如果已经等于0了,就会让进程处于阻塞状态,一直在while循环里面,这种现象称之为忙等。这样不断的循环不断的循环很明显浪费了CPU的时钟,本来这个CPU时钟可以有效的给其他进程使用。这种类型的信号量也叫做自旋锁。因为进程在等待锁开始的时候,还在不断的去访问那个临界条件。
自旋锁的优点
自旋锁的优点就在于进程在等待锁的过程中不进行上下文的切换,因为上下文切换本身就要花费很多的时间,如果锁占用的时间短,那么自旋锁显然是很有用的,自旋锁通常被用于多处理器系统中,这样就是一个线程在一个处理器上自旋的时候,另一个线程可以在另一处理器上执行过了判断条件的下面的那段代码。
为了克服这种忙等现象,我们可以去修改信号量操作wait() 和signal()的定义,当一个进程执行了wait()操作的时候,发现信号量值不为正的时候,那么这个进程必须等待,当然这个等待是这样的不是说我这个进程去盲等,而是去阻塞这个进程进去访问那个资源,也就是说阻塞操作其实指的就是将一个进程放入到就绪队列中。
比如说为了实现这种信号量的定义,我们可以将这个信号量定义为一个结构体。
typedef struct {
int value;
struct process *list;
}
也就是说每一个信号量都会有一个整数值以及一个进程链表,当一个进程需要去等待信号量的时候,就会把它加入到进程链表中,当我们使用signal()函数的时候,如果说发现链表中有等待的进程,我们就会去调用一个方法去唤醒这个进程
wait()方法的定义
wait(semaphore *S){
S->value--;
if(S->value<0){
//添加进程到链表中
//挂起调用它的进程
block();
}
}
signal()方法
signal(semaphore *S)
{
S->value++;
if(S->value<=0){
//移除进程在进程链表中
//唤醒进程
wakeup(P);
}
}
在这里信号量是可以为负了,如果信号量为负数的话,那么它的绝对值就是等待该信号量的进程的个数。等待进程的链表可以利用进程控制块PCB中来进行实现。每个信号量中都有一个整型值和一个PCB链表的指针。