首先先来认识一下进程调度过程中经常用到的概念,临界资源和临界区。
临界资源:系统中某些资源一次只允许一个进程使用。
临界区:不管是硬件临界资源还是软件临界资源,多个进程必须互斥对其进行访问,每个进程中访问临界资源的代码称为临界区。
/**
* 访问临界资源的循环进程描述
**/
while(true){
进入区 //检查是否能够访问临界资源的代码
临界区 //对临界资源进行互斥访问
退出区 //结束访问临界资源的代码
剩余区 //临界区及退出区以外的代码
}
同步机制应遵循的规则:
- 空闲让进
- 忙则等待
- 有限等待(应该保证在有限时间内能够进入)
- 让权等待(当进程不能进入自己的临界区时,立即释放处理机,以免进程陷入“忙等”状态
信号量机制
Dijstktra提出整型信号量以来,信号量机制得到了很大的发展:
从整型信号量经记录型信号量,进而发展为**“信号集”机制**
信号量机制是一种有效的进程同步机制。
**整型信号量:**并未遵循 “让权等待” 的原则,使进程处于 “忙等” 状态
//P, V操作是原子操作,在一个进程修改信号量时,另一个进程不能进行修改
//整型信号量表示一个用于表示资源数目的整形量S
//P操作是「通过」
wait(S){
while(S <= 0);
S--;
}
//V操作是「释放」
signal(S){
S++;
}
记录型信号量:不存在 ”忙等“,采取 ”让权等待“ 原则
using semaphore = struct{
int value; //value表示某类资源的数量,如果value的值是1的话,表示只允许一个进程访问临界资源,此时信号量为互斥信号量
struct process_control_block * list; //进程指针链表,存储等待进程,「记录型信号量」由此得名
};
wait(semaphore * S){
S->value--; //使某一类资源数量减1
if(S->value < 0) block(S->list); //当S->value < 0时,表示已分配完成,此时调用block原语进行自我阻塞,并放弃处理机,将进程插入到信号量链表S中
}
signal(semaphore * S){
S->value++; //释放某一类资源,使其数量加1
if(S->value <= 0) wakeup(S->list); //S->value <= 0意味着此时等待链表还有任务等待中,唤醒第一个等待进程
}
AND型信号量:AND同步机制的基本思想是:将进程在整个运行过程中需要的所有资源,一次性全部分配给进程,待进程使用完之后再一起释放,以避免死锁。
Swait(S1, S2, ..., Sn){
while(true)
if(S1 >= 1 && S2 >= 1 && ... && Sn >= 1){
for(int i = 1; i <= n; ++i)
Si --;
break;
}
else{
放到等待队列中等待
}
}
Signal(S1, S2, ..., Sn){
while(true){
for(int i = 1; i <= n; ++i)
Si++;
将进程从等待队列移出,使其进行
}
}
信号量集:对AND机制进行扩充,对进程申请的所有资源以及每类资源的不同的资源需求量,在一次P,V原语操作中完成
Swait(S1, t1, d1, ..., Sn, tn, dn)
Ssignal(S1, t1, d1, ..., Sn, tn, dn)
使用信号量机制实现进程互斥:为该资源设置一互斥信号量
mutex
,设置初值为1,将个进程访问该资源的临界区CS置于
wait(mutex)
和
signal(mutex)
中
//P操作和V操作必须成对出现,否则:
//1. 缺少wait(mutex)将会导致系统混乱,不能保证对临界资源的互斥访问
//2. 缺少signal(mutex)将会使临界资源得不到释放
semaphore mutex = -1;
P(){
while(true){
wait(mutex);
临界区;
signal(mutex);
剩余区;
}
}