信号量机制分为整型信号量机制和记录型信号量机制。
整型信号量机制
整型信号量机制:用一个整数型变量来作为信号量,数值表示的是某种资源的数目。相对比于普通整型变量,它只能进行初始化,P,V三种操作。
P操作:wait原语(一气呵成,不可以被中断滴,是由关中断和开中断的指令实现的。)P:Proberen(荷兰语中的增加)
V操作: signal原语V:verhogen(荷兰语中的测试)
int S=1;//整型信号量,代表某种资源的数目。
void wait(int S) #相当于进入区
{
while(S<=0); //资源数不足,循环进入等待
S=S-1; //申请到资源,对应资源减一
}
void signal(int S) #相当于退出区
{
S=S+1; //使用完资源之后,释放对应资源
}
wait和signal都是一气呵成滴,不可以被打断,整型信号量机制的缺点是“不满足让权等待,假设拿不到cpu的处理器,就会一直占用资源“。
记录型信号量
记录型信号量:可以解决让权等待滴问题
记录型信号量的定义如下所示:
typdef struct
{
int value; //剩余资源数
struct process *L; //等待队列
} semaphore/ˈseməfɔː(r)/;信号量
wait原语和signal原语
void wait(semaphore S)
{
S.value--;
if(S.value <0)
block(S.L);
}
void signal(semaphore S)
{
S.value++;
if(S.value <=0)
wakeup(S.L);
}
wait原语中,对信号量S进行一次P操作,对应的资源数就会减1。如果S.value小于0,代表该资源已经分配完毕了,因此进程会调用block原语进行自我阻塞(由运行态变为阻塞态),并且该进程会被插入到信号量S的等待队列L中。
signal原语中,对信号量S进行一次V操作,因为释放了资源,因此value值会加1。如果加1之后对应的value仍然<=0,代表有进程在等待该资源,因此会从唤醒等待队列中的第一个的进程(该进程会由阻塞态变为就绪态)
看一个例子哦:
假设有两台打印机资源,初始值可以将S.value的值设为2.
假定一开始P0拿到时间片,执行P操作,S.value变为1,之后发生进程切换,P1拿到时间片,执行P操作,S.value为0。之后进程切换,P2拿到时间片,执行P操作,S.value的值变为-1,由于此时已经没有资源了,故P2进程会进入到阻塞状态,同时P2进程被插入到S中的L等待队列中。同理P3拿到时间片,S.value的值变为-2,P3进程进入到阻塞状态并且被插入到等待队列中。
此时P0重新拿到时间片,使用打印机,之后进行V操作,S.value值+1,变为-1。即代表此时等待队列中有进程,因此会掉用wakeupy原语唤醒第一个进程P2,P2由阻塞状态变为就绪状态。之后P1使用完毕之后,释放相关资源,执行V操作,对应的S.value值变为0。代表此时等待队列L中依旧有进程,会调用wakeup原语唤醒P3。
之后P2拿到时间片,使用打印机资源,释放相关资源进行V操作,value值变为1,即此时等待队列中是没有进程滴。P2运行结束,P3拿到时间片,从就绪状态到运行状态,使用打印机的资源,使用完毕之后进行V操作,value的值变为2。整个过程就是这样滴哦!
信号量机制实现进程互斥
- 分析问题,设置临界区
- 设置互斥信息量,初值为1
- 临界区之前对信号量执行P操作
- 临界区之后对信号量执行V操作
需要注意的是,如果有多个临界资源,就需要设置多个不同的互斥信号量,它的英文表示是mutex。
P,V需要成对出现,缺少P不能保证临界资源的互斥访问,缺少V就无法释放资源啦!P,V操作本质上就是加锁和解锁操作。
信号量机制实现进程同步
进程同步:由于进程之间是并发执行的,所以进程中代码的顺序是不可预知的,在某些情况下,我们需要让进程的执行顺序按照我们想要的逻辑来运行,因此就需要使用信号量机制来实现同步机制。
进程同步的方法:
- 设置同步信号量S,初始值为0
- 需要在前面运行的代码,后面增加V操作。
- 需要在后面运行的代码,在该代码之前增加P操作。
分析如下的两个进程:
假设我们需要让代码2在前面运行,代码4在后面运行。需要进行如上所示的设置。
考虑以下两种情况,均可以实现代码2先于代码4运行。