二十、信号量机制
1.信号量机制
进程互斥的四种软件实现方式和进程互斥的三种硬件实现方式都存在一些问题。
1.在双标志检查法中,进入区的“检查”、‘上锁”操作无法一气呵成,从而导致了两个进程有可能同时进入临界区的问题。
2.所有的解决方案都无法实现“让权等待”。
1965年,荷兰学者迪杰斯特拉Dijkstra提出了一种卓有成效的实现进程互斥、同步的方法–信号量机制
用户可以通过使用操作系统提供的一对原语来对信号量进行操作,从而很方便地实现了进程互斥、进程同步。信号量其实就是一个变量(可以是一个整数,也可以是更复杂的记录型信号量),可以用一个信号量来表示系统中的某种资源的数量。
原语是一种特殊的程序段,其执行只能一气呵成,不可被中断。原语是由关中断/开中断指令实现的,软件解决方案的主要问题是由“进入区的各种操作无法一气呵成”,因此如果能把进入区、退出区的操作都用原语实现,使这些操作能“一气呵成”就能避免问题。
一对原语:wait(s)原语和signal(s)原语,可以把原语理解为我们自己写的函数,函数名称分别为wait和signal,括号里的信号量s其实就是函数调用时传入的一个参数。wait、signal原语常简称为P,V操作(来自荷兰语proberen和verhogen,意思是“尝试”和“增加”)。因此,wait(s)和signal(s)可被简写为P(s)和V(s)。
2.整形信号量
用一个整数型的变量作为信号量,来表示系统中的某种资源的数量。
如:某计算机系统中有一台打印机:
1 | int S = 1; //初始化整形信号量S,表示当前系统中可用的打印机资源数 |
信号量其与普通整数变量的区别:对信号量的操作只有三种,即 初始化、P操作、V操作。在wait原语中,“检查”和“上锁”一气呵成,避免了并发、异步导致的问题。但是如果信号量S为0,则其中的while语句会一直检查信号量S的占用情况,会一直占用处理机,不满足“让权等待”原则,会发生“忙等”。
3.记录型信号量(重要)
整形信号量的缺陷是存在“忙等”的问题,因此人们又提出了“记录型信号量”,即用记录型数据结构表示的信号量。
1 | //信号量的数据结构 |
例:某计算机系统中有两台打印机,则可在初始化信号量S时将S.value的值设为2,队列S.L设置为空
1 | typedef struct{ |
wait(S)和signal(S)也可记为P(S),V(S),这对原语用于实现系统资源的申请和释放。
S.value的初始值表示系统中某种资源的数目。
对信号量S的一次P操作意味着进程请求一个单位的该类资源,因此需要执行S.balue–,表示资源数减1,当S.value<0时表示该类资源已分配完毕,因此进程应调用block原语进行自我阻塞(当前运行的进程从运行态切换为阻塞态),主动放弃处理机,并插入该类资源的等待队列S.L中。可见,该机制遵循了“让权等待的原则”,不会出现“忙等”的现象。
对信号量S的一次V操作意味着进程释放一个单位的该类资源,因此需要执行S.value++,表示资源数+1,若加一后仍是S.value<=0,表示依然有进程在等待该类资源,因此应调用wakeup原语唤醒等待队列中的第一个进程(被唤醒的进程从阻塞态切换为就绪态)。