操作系统---进程同步与互斥
文章目录
- 1. 同步与互斥的基本概念
- 1.1 进程同步
- 1.2 进程互斥
- 1.2.1 临界资源与临界区
- 1.2.2 互斥概念
- 1.3 同步机制的准则
- 1.4 总结
- 2. 进程互斥的实现方法
- 2.1 软件实现方法
- 2.1.1 单标志法
- 2.1.2 双标志法先检查
- 2.1.3 双标志法后检查
- 2.1.4 Peterson's Algorithm
- 2.2 硬件实现方法
- 2.2.1 中断屏蔽方法
- 2.2.2 TestAndSet指令(检查和上锁)
- 2.2.3 Swap指令
- 2.2.4 总结
- 3. 信号量机制
- 3.1 介绍
- 3.2 整型信号量
- 3.3 记录型信号量
- 3.4 总结
- 4. 用信号量机制实现进程互斥、同步、前驱关系
- 4.1 实现进程互斥
- 4.2 实现进程同步
- 4.3 实现前驱关系
- 4.4 总结
1. 同步与互斥的基本概念
1.1 进程同步
- 在多道程序环境下,进程是并发执行的,不同进程之间存在着不同的相互制约关系。为了协调进程之间的相互制约关系,引入了进程同步的概念
- 同步亦称直接制约关系,是指为完成某种任务而建立的两个或多个进程,这些进程因为需要在某些位置上协调它们的工作次序而等待、传递信息所产生的制约关系
- 进程间的直接制约关系源于它们之间的相互合作
1.2 进程互斥
1.2.1 临界资源与临界区
- 虽然多个进程可以共享系统中的各种资源,但其中许多资源一次只能为一个进程所用,我们将一段时间内一次仅允许一个进程使用的资源称为临界资源
- 许多物理设备都属于临界资源,如打印机等。此外,还有许多变量、数据等都可以被若干进程共享,也属于临界资源
- 对临界资源的访问必须互斥地进行,每个进程中,访问临界资源的那段代码就是临界区
可以把临界资源的访问过程分成4个部分:
①进入区:在进入区要检查是否能进入临界区,若能,则需设置正在访问临界区的标志,以阻止其他进程同时进入临界区,不能则需等待
②:临界区:进程访问临界资源的那段代码
③:退出区:将正在访问临界区的标志清除
④:剩余区:代码的其余部分
do{entry section; // 进入区 critical section; // 临界区 exit section; // 退出区 remainder section; // 剩余区
} while(true);
1.2.2 互斥概念
- 互斥也称间接制约关系。当一个进程进入临界区使用临界资源时,另一个进程必须等待,当占用临界资源的进程退出临界区后,另一个进程才允许去访问此临界资源
eg:在仅有一台打印机的系统中,有两个进程A和进程B,若进程A需要打印时,系统已将打印机分配给进程B,则进程A必须阻塞。一旦进程B将打印机释放,系统便将进程A唤醒,并将其由阻塞态变为就绪态
1.3 同步机制的准则
为禁止两个进程同时进入临界区,同步机制应遵循以下准则:
①:空闲让进 ②:忙则等待
③:有限等待 ④:让权等待
1.4 总结
2. 进程互斥的实现方法
2.1 软件实现方法
2.1.1 单标志法
思想:设置一个公用整型变量turn,用于指示被允许进入临界区的进程编号,即turn = 0,则允许P0进程进入临界区
P0进程: P1进程:
while(turn != 0) while(turn != 1) // 进入区
{ ; } { ; } // 一直循环执行空语句
critical section; critical section; // 临界区
turn = 1; turn = 0; // 退出区
remainder section; remainder section; // 剩余区
优点:可以确保每次只允许一个进程进入临界区
缺陷:虽可以确保每次只允许一个进程进入临界区,但两个进程必须交替进入临界区。即,若当turn = 0时,允许P0进程进入,但P0进程却不进入,此时P1进程发出进入临界区的请求,虽然临界区空闲,但P1进程却无法进入临界区。违反了空闲让进和让权等待,容易造成资源利用不充分。
2.1.2 双标志法先检查
思想:在每个进程访问临界区资源之前,先检查看临界资源是否正被访问,若正被访问,则该进程需等待;否则,进程才进入自己的临界区。基于此,设置一个bool类型数组flag,若flag[i] = false,表示Pi进程未进入临界区,反之,则表示进入
Pi进程: Pj进程:
while(flag[j]) ① while(flag[i]) ② // 进入区
{ ; } { ; }
flag[i] = true; ③ flag[j] = true; ④ // 进入区
critical section; critical section; // 临界区
flag[i] = false; flag[j] = false; // 退出区
remainder section; remainder section; // 剩余区
优点:不用交替进入,可连续使用;
缺点:Pi与Pj可能同时进入临界区。按序列①②③④执行时,会同时进入临界区。违反了忙则等待和让权等待。即在检查对方的flag后和切换自己flag前有一段时间,结果都检查通过,出现此问题是由于在检查和修改进入临界区标志的操作不能一气呵成。
2.1.3 双标志法后检查
思想:与双标志法先检查顺序不同,双标志法后检查是先将自己的标志设置为true,再检测对方的状态标志,若对方标志为true,则进程等待;否则进入临界区
Pi进程: Pj进程:
flag[i] = true; ① flag[j] = true; ② // 进入区
while(flag[j]) ③ while(flag[i]) ④ // 进入区
{ ; } { ; }
critical section; critical section; // 临界区
flag[i] = false; flag[j] = false; // 退出区
remainder section; remainder section; // 剩余区
优点:不用交替进入,可连续使用,解决了两进程同时进入临界区的问题;
缺点:同样当按序列①②③④执行时,即两个进程几乎同时都想要进入临界区时,它们分别将自己的标志值flag设置为true,并同时检测对方的状态,发现对方也想要进入临界区,双方互相谦让,结果谁也进入不了临界区,从而导致“饥饿”现象。违反了空闲让进、有限等待和让权等待。
2.1.4 Peterson’s Algorithm
思想:基于双标志法后检查的基础上,又设置了变量turn,即每个进程在先设置自己的标志后再设置turn标志。然后再同时检测另一个进程状态标志(flag)和允许进入标志(turn),以便保证两个进程同时要求进入临界区时,只允许一个进程进入临界区。
Pi进程: Pj进程:
flag[i] = true;turn = j; ① flag[j] = true;turn = i; ② // 进入区
while(flag[j]&&turn==j) ③ while(flag[i]&&turn==i) ④ // 进入区
{ ; } { ; }
critical section; critical section; // 临界区
flag[i] = false; flag[j] = false; // 退出区
remainder section; remainder section; // 剩余区
优点:不用交替进入,可连续使用,解决了两进程同时进入临界区和进程饥饿的问题
缺点:违反了让权等待。
对于Peterson’s Algorithm的理解:其实可以这么来看,先设置flag为true,是表达自己想要进入临界区的请求,再设置turn为另一个进程编号,是表示自己的一个谦让行为,若两个进程同时到达,即当按序列①②③④执行时,此时先谦让者,先进入。即①的时候true=j,表示Pi进程的谦让,②的时候true=i,表示Pj进程的谦让,然后运行③检查时发现,turn!=j,此时先谦让的Pi先进入临界区,这就是先谦让者,先进入。满满的人情世故的体现。
总结来看:本算法是算法一与算法三的结合。利用flag解决临界资源的互斥访问,而利用trun解决“饥饿”问题。
2.2 硬件实现方法
2.2.1 中断屏蔽方法
利用 “开/关中断指令” 实现(与原语的实现思想相同,即在某进程开始访问临界区到结束访问为止都不允许被中断,也就不能发生进程切换,因此也不可能发生两个同时访问临界区的情况)
2.2.2 TestAndSet指令(检查和上锁)
- 简称 TS 指令,也有地方称为 TestAndSetLock 指令,或 TSL 指令
- TSL 指令是用硬件实现的,执行的过程不允许被中断,只能一气呵成。以下是用C语言描述的逻辑
// bool型共享变量 lock 表示当前临界区是否被加锁
// true 表示已加锁,false 表示未加锁
bool TestAndSet(bool* lock)
{bool old; old = *lock; // 先保存之前的状态 *lock = true; // 无论如何,当前状态为true return old; // 返回之前状态
}
用该指令实现互斥的过程描述如下:
// 以下是使用TSL指令实现互斥的算法逻辑
while(TestAndSet(&lock)) // 上锁并检查
{ ; }
临界区代码段...
lock = flase; // 解锁
剩余区代码段...
2.2.3 Swap指令
- 有的地方也叫 Exchange 指令,或简称 XCHG 指令。
- Swap 指令是用硬件实现的,执行的过程不允许被中断,只能一气呵成。以下是用C语言描述的逻辑
// Swap 指令的作用是交换两个变量的值
Swap(bool* a,bool* b)
{bool temp;temp = *a;*a = *b;*b = temp;
}
用该指令实现互斥的过程描述如下:
// 以下是 Swap 指令实现互斥的算法逻辑
// lock 表示当前临界区是否被加锁
bool old = true;
while(old == true)Swap(&lock,&old);
临界区代码段...
lock = false;
剩余区代码段...
2.2.4 总结
3. 信号量机制
3.1 介绍
3.2 整型信号量
用一个整数型的变量S作为信号量,用来表示系统中某种资源的数量,wait和signal操作可描述为:
wait(S){ // wait原语,相当于“进入区” while(S<=0) ; // 如果资源数不够,就一直循环等待 S = S - 1; // 如果资源数够,则占用一个资源
}
signal(S){ // signal原语,相当于“退出区” S = S + 1; // 使用完资源后,在退出区释放资源
}
缺点:无法实现让权等待
3.3 记录型信号量
整型信号量的缺陷是存在“忙等”问题,因此人们又提出了“记录型信号量”,即用记录型数据结构表示的信号量,描述如下:
// 记录型信号量的定义
typedef struct{int value; // 剩余资源数 struct process *L; // 等待队列
} semaphore;
wait和signal操作可描述为:
void wait(semaphore S){ // 相当于申请资源 S.value--;if(S.value<0){ // 没有资源就阻塞 add this process to S.L;block(S.L);}
}
void signal(semaphore S){ // 相当于释放资源 S.value++;if(S.value<=0){ // 还有进程在阻塞队列则需唤醒 remove a process P from S.L;wakeup(S.L);}
}
举例:
3.4 总结
4. 用信号量机制实现进程互斥、同步、前驱关系
4.1 实现进程互斥
互斥的P、V进行是一个进程内部的局限行为。在互斥的实现中,临界区访问前需紧跟P(细节)。即在互斥问题中,P、V操作要紧夹使用互斥资源的那个行为,中间不能有其他冗余代码
信号量 mutex 表示“进入临界区的名额”,实现进程互斥的步骤如下:
- 分析并发进程的关键活动,划定临界区(如:对临界资源打印机的访问就应放在临界区)
- 设置互斥信号量 mutex,初值为 1
- 在进入区 P(mutex)——申请资源
- 在退出区 V(mutex)——释放资源
4.2 实现进程同步
四字真言:前V后P。同步的P、V进行是两个进程间的相互配合
4.3 实现前驱关系
4.4 总结
参考:《王道计算机考研 操作系统》
bilibili: https://www.bilibili.com/video/av70156862