操作系统之信号量机制详解
临界区相关概念
-
一段进程访问临界区的代码:
do{ entry section; //进入区 critical section; //临界区 exit section; //退出区 remainder section; //剩余区 }while(true);
-
临界区是进程中访问临界资源的代码段。
-
进入区和退出区是实现互斥的代码。
-
临界区也可称为“临界段”。
-
对临界区的访问应遵循以下原则:
- 空闲让进。临界区空闲时,可以允许一个请求进入临界区的进程立即进入临界区。
- 忙则等待。当已有进程进入临界区时,其它试图进入临界区的进程必须等待。
- 有限等待。对请求访问的进程,应能保证在有限时间内进入临界区(保证不会饥饿)。
- 让权等待。当进程不能进入临界区时,应立即释放处理机,防止进程忙等待。
信号量的相关概念
用户进程可以通过使用一个变量(可以是一个整数,也可以是更为复杂的记录型变量),可以用一个信号量来表示系统中某种资源的数量。比如:系统中只有一台打印机,就i可以设置一个初值为1的信号量。
原语是一种特殊的程序段,其执行只能一气呵成,不可被中断。原语是由关中断/开中断指令实现的。
wait(S)原语和signal(S)原语,可以把原语理解成我们自己写的函数,函数名分别为wait和signal,括号里的信号量S其实就是函数调用时传入的一个参数。
wait和signal原语简称P、V操作(来自荷兰语proberen和verhogen)。
整型信号量机制
/**
例如:操作系统中只有一台打印机。。。。
**/
int S=1;
//wait原语相当于"进入区"
void wait(int S){
if(S<=0) while;//如果资源不够,就一直循环等待,但是会导致忙等
S--;//如果资源数够,占用一个资源
}
//signal原语相当于"退出区"
void signal(int S){
S=S+1;//使用完资源后,在退出区释放资源
}
纪录型信号量机制
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(semphore S){
S.value++;
if(S.value<=0){
wakeup(S.L);//释放资源后,若有进程在等待这种资源,则使用wakeuo原语唤醒等待队列中的一个进程,该进程从阻塞态变为就绪态
}
}
信号量机制实现互斥
步骤:
- 分析并发进程的关键活动,划定临界区
- 设置互斥信号量
mutex
- 在临界区之前执行P(
mutex
) - 在临界区之后执行V(
mutex
)
实例:
semaphore mutex=1;
p1(){
....
P(mutex);
临界区代码
V(mutex);
....
}
P2(){
....
P(mutex)
临界区代码
V(mutex)
}
注意:
- 对不同的临界资源需要设置不停的互斥信号量。
- P、V操作必须成对出现。缺少P(
mutex
)就不能保证临界资源实现互斥访问。缺少V(mutex
)就会导致资源永不被释放,等待进程永不被唤醒。
信号量机制实现同步
进程同步:要让各并发进程按要求有序地推进。
步骤:
- 分析什么地方需要实现"同步关系",即必须保证"一前一后"执行的两个操作(或两句代码)。
- 设置同步信号量S,初始为0。
- 在"前操作"后执行V(S)。
- 在"后操作"前执行P(S)。
实例:保证代码4运行在代码1和代码2之后。
semaphore S=0;
P1(){
代码1;
代码2;
V(S);
代码3;
}
P2(){
P(S);
代码4;
代码5;
代码6;
}
上述代码,若先执行到V(S)操作,则S++后S=1。之后当执行到P(S)操作时,由于S=1,表示有可用资源,会执行S–,S的值变为0,P2进程不会执行block原语,而是继续往下执行代码4。
若先执行到P(S)操作,由于S=0,S–后S=-1,表示此时没有可用资源,因此P操作中会执行block原语,主动请求阻塞。之后当执行完代码2,继而执行V(S)操作,S++,使S变回0,由于此时有进程在该信号量对应的阻塞队列中,因此会在V操作中执行wakeup原语,唤醒P2进程进入就绪对列。
信号量机制实现前驱关系
步骤:
- 要为每一对前驱关系各设置一个同步变量
- 在"前操作"之后对相对应的同步变量执行V操作
- 在“后操作”之前对相对应同步变量执行P操作
前驱关系本质上是更复杂的同步问题。
生产者消费者问题
生产者、消费者共享一个初始为空、大小为n的缓冲区。
只有缓冲区没满时,生产者才可以把产品放入缓冲区,否则必须等待。
只有缓冲区不为空时,消费者才可以从缓冲区取出产品,否则必须等待。
semaphore mutex=1;
semaphore empty=n;
semaphore full=0;
void producer(){
生产一个产品
P(empty);
P(mutex);
把产品放入缓冲区
V(mutex);
V(full);
}
void customer(){
P(full);
P(mutex);
从缓冲区中取出产品
V(mutex);
V(empty);
使用产品
}