操作系统自学(十二)信号量机制及用信号量机制实现进程互斥 同步 前驱关系

信号量机制

在这里插入图片描述

用户可以通过使用操作系统提供的一对原语来对信号量进行操作,从而很方便的实现了进程互斥,进程同步

信号量其实就是一个变量(可以使一个整数 也可以是更复杂的记录型变量),可以用一个信号量来表示系统中某种资源的数量
例如:系统中只有一台打印机,就可以设置一个初值为1的信号量

整型信号量

用整数型的变量作为信号量,用来表示系统中某种资源的数量
例如计算机系统中有一台打印机

与普通的整数变量的区别:
对信号量的操作只有三种 初始化,P操作,V操作

int S=1;//初始化整型信号量S 表示当前系统中可用打印机资源数

void wait(int S){//wait原语 相当于进入区
	while(S<=0);//如果资源数不够 就一直等待循环
	S--;//如果资源数够 占用一个资源
}

void signal(int S){//signal 原语 相当于退出区
	S++;//使用完资源之后 在退出区释放资源
}

进程P0
……
wait(S);//进入区 申请资源
使用打印机资源 //临界区 访问资源
signal(S);//退出区 释放资源


进程P1
wait(S);//进入区 申请资源
使用打印机资源 //临界区 访问资源
signal(S);//退出区 释放资源

上述代码中的检查和上锁一气呵成,完美的避免了并发,异步导致的问题
但是现在还存在不满足“让权等待”的原则 可能发生“忙等”

记录型信号量

由于整型信号量存在忙等的问题,因此人们又提出来记录型信号量
使用记录型数据结构表示的信号量

typedef struct{
	int value;//剩余资源数
	struct process *L;//等待队列
} semaphore;

//某进程想要使用资源的时候 通过wait原语申请
void wait (semaphore S){
	S.value--;
	if(S.value < 0){
		block(S.L);
		//如果剩余资源数不够,使用block原语使得进程从运行态转换到阻塞态
		//并把挂到信号量S的等待队列中
	}
}

//进程使用完资源之后,通过signal原语释放
void signal(semaphore S){
	S.value++;
	if(S.value<=0){
		wakeup(S.L);
		//释放资源之后,若还有别的进程等待这种资源,则使用wakeup原语唤醒等待队列中的一个进程
		//该进程从阻塞态变为就绪态
	}
}

上述wait(S) 可以记为P(V) signal(S)可以记为V(S)

对信号量S的一次P操作意味着进程请求一个单位的该类资源,因此需要执行S.value–
资源数S.value<0的时候 表示资源分配完毕 因此需要调用block原语进行自我阻塞(从运行态转化为阻塞态)
主动放弃处理机并插入该类资源的的等待队列S.L中
可见该机制遵循了让权等待的原则

对信号量S的每一次V操作 意味着释放一个单位的该类资源
因此执行S.value++ 表示资源数加一
如果加一之后仍然是S.value<=0 表示依然有进程在等待该类资源
此时 应调用wakeup来唤醒队列中的第一个进程(从阻塞态转换到就绪态)

以上内容就是信号量机制的介绍。

信号量机制实现进程互斥 同步 前驱关系

进程互斥

设置初始互斥信号量mutex 初始值为1

//信号量机制实现互斥
semaphore mutex=1;//初始化信号量

p1(){
	P(mutex);//使用临界资源之前需要加锁
	临界区代码段
	V(mutex);//使用临界区资源后需要解锁
}

p2(){
	P(mutex);//使用临界资源之前需要加锁
	临界区代码段
	V(mutex);//使用临界区资源后需要解锁
}

对不同的临界资源需要设置不同的互斥信号量
P V必须成对出现
缺少P操作就不能保证临界资源的互斥访问
缺少V操作会导致资源永远不被释放,进程永不被唤醒

进程同步

进程同步必须保证 ** “一前一后” ** 的操作
所以我们要在前操作之后执行 ** V(S) **
后操作之前执行 ** P(S)**

可能我们现在还是一头雾水 看完我下面的解释你就会豁然开朗

semaphore S=0;//初始化同步信号量 初始值为0
P1(){
	代码1;
	代码2;
	V(S);
	代码3;
}

P2(){
	P(S);
	代码1;
	代码2;
	代码3;
}

假如我们预想的情况是先执行P1中的代码1和代码2
之后执行P2中的代码

但是现实情况中,如果我们不做任何操作 很有可能出现P2中的代码在P1之前执行
所以我们可以看到代码中加入了**V(S)和P(S)**语句

在V(S)语句执行之前 并没有处理机给P2使用的 所以P2处于阻塞状态
当P1中的代码2执行完成 释放处理机 这样P2就可以执行了

前驱关系


实质上前驱图的实现和进程同步的思路是一样的
我们在使用之前 再释放对应的处理机 这样就可以很有效的实现想要的功能了
S1到S2对应a=0
S1到S3对应b=0
S2到S4对应c=0
S2到S5对应d=0
S4到S6对应e=0
S5到S6对应f=0
S3到S6对应g=0

上边的前驱图就如下面代码所示

p1(){
	S1;
	V(a);
	V(b);
}
p2(){
	P(a);
	S2;
	V(c);
	V(d);
}
p3(){
	P(b);
	S3;
	V(g);
}
p4(){
	P(c);
	S4;
	V(e);
}
p5(){
	P(d);
	S5;
	V(f);
}
p6(){
	P(e);
	P(f);
	P(g);
	S6;
}

以上就是使用信号量机制实现前驱关系的内容

这篇博文就介绍这么多 希望对大家有帮助

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值