在进程管理(3):同步互斥中,我们提到,为了解决同步互斥问题,操作系统会提供一些高级抽象方法供应用进程调用,这样应用进程就不需要自己利用繁琐的软件方法来解决同步互斥了。存在三种高级抽象方法,分别是锁,信号量与条件变量,其中锁也在上面那篇中讨论过了,这里主要是讨论信号量与条件变量。同步互斥的层次结构如下图所示:
信号量
为什么要引入信号量?
前面我们已经用锁机制方便的解决了临界区的互斥访问问题了,但是锁机制似乎并不能适用于更一般的情况。例如操作系统某些资源是有多个的,可以让多个进程共同访问,只有当访问的进程多于资源数量的时候才对进程访问加以限制。很明显,对于这种情况,使用锁机制的结果是一次只能有一个进程访问资源,这无疑是对资源的一种浪费。
此外,锁机制只能解决进程间的互斥访问问题,并不能推广到进程间的同步问题。
因此,有必要引入一个新的机制,既可以合理地对多个资源进行分配,又可以同时兼顾同步与互斥问题。这就是引入信号量机制的原因。
什么是信号量?
信号量是操作系统提供的一种协调共享资源访问的方法。它采用一种简明的方法来协调多个资源的访问,即采用信号量来表示当前剩余的系统资源数量。如果有一个进程请求某个资源,则将该资源的信号量递减;反之,如果某个进程释放了资源,就将该资源的信号量递增。这里对信号量的操作都是被操作系统封装起来的,因此可以看到,信号量更多的是一种抽象数据类型,由一个整型变量和两个基本操作组成,通常用P
操作和V
操作来表示递减和递增,分别对应了荷兰语里面的尝试减少
和增加
。由于PV
操作的对象,即sem
变量实质上也是一个共享变量,对它的访问是需要互斥进行的,因此这里的PV
操作都需要是原子操作。
class Semaphore{
private:
int sem;
public:
void up();
void down();
}
信号量的实现
为了实现信号量,主要就是需要实现它的两个成员函数down
和up
。
当一个进程请求资源时,首先应该检查sem
当前的值,如果sem > 0
,表示系统还有剩余的资源,则执行--sem
并且将资源分配给请求进程;如果sem <= 0
,则表示当前系统已经没有这种资源了,仍然执行--sem
,表示新增了一个进程在等待这个资源。由于当前进程得不到它请求的资源,此时应该释放CPU的控制权,调度其他进程进入运行状态。为了在资源空闲时可以唤醒当前进程,可以对每个信号量设置一个等待队列,并且将等待该信号量的进程加入等待队列中。
当一个进程释放了它占用的资源时,首先应该执行++sem
表示将一个资源归还给了操作系统。此时如果sem <= 0
,则表示还有其他进程在等待这个资源,所以应该从等待队列中挑选一个进程,将其唤醒;否则,如果sem > 0
,表示当前已经有空闲的资源了,所以没有进程在等待队列中,直接退出就可以了。
根据这里的分析,给出上面Semaphore
类的伪代码实现:
class Semaphore{
private:
int sem;
WaitQueue q;
public:
void up();
void down();
}
void Semaphore::down(){
--sem;
if(sem < 0){
add current process to q;
schedule();
}
}
void Semaphore::up(){
++sem;
if(sem <= 0){
pick a process in q;
wakeup_proc();
}
}
需要指出的是,上面的代码只是示意而已,实际的实现中还有一些细节的部