1.基本概念
- 在多道程序环境下,进程是并发执行的,不同进程之间存在着不同的相互制约关系。为了协调进程之间的相互制约关系,如等待、传递信息等,引入了进程同步的概念。进程同步是为了解决进程的异步问题。
- 一个简单的例子来理解这个概念。
- 例如,让系统计算1 + 2x3,假设系统产生两个进程: 一个是加法进程,一个是乘法进程。要让计算结果是正确的,一定要让加法进程发生在乘法进程之后,但实际上操作系统具有
异步性
,若不加以制约,加法进程发生在乘法进程之前是绝对有可能的,因此要制定一定的机制去约束加法进程,让它在乘法进程完成之后才发生。
(一) 临界资源
- 系统中的许多资源虽然可以被进程共享使用,但是也存在一些一次只能被一个进程使用的资源,如打印机
将系统中一次只能被一个进程使用的资源称为临界资源
- 而在程序中对临界资源进行访问的那段代码,成为临界区
(二) 互斥
互斥,亦称间接制约关系
。进程互斥
指当一个进程访问某临界资源时,另一个想要访问该临界资源
的进程必须等待。当前访问临界资源的进程访问结束,释放该资源之后,另一个进程才能去访问临界资源。
(三) 同步机制原则
为了禁止两个进程同时出现在临界区,应满足下列准则:
- 空闲让进 : 当临界区空闲时,可以让请求的进程直接进入使用
- 忙则等待 : 当临界区已被占用,则请求的进程必须等待
- 有限等待 : 对请求访问临界区的进程,应保证在有限时间内进入临界区
- 让权等待 : 当进程不能进入临界区时,应该让出处理器,防止阻塞发送
2.临界区互斥的软件层面
(一) 单标志法
- 思想:两个进程在访问完临界区资源后,会把临界区的权限交给另一个进程
-
漏洞:若现在turn=0,也即p0可以进入临界区,但是p0因为特殊原因迟迟不进入,导致p1也无法进入,
这就违背了’空闲让进原则’
(二) 双标志先检查法
- 设置一个布尔型数组flag[],flag[i]为true就代表进程i想要进入临界区.每个进程在进入临界区之前,先检查有没有别的进程要进入,没有则将flag[i]设置为true:
-
漏洞:若p0,p1同时进入,同时将flag设置为true,就违背了’忙则等待的原则’
-
原因:检测和上锁不是一气呵成的,导致在上锁前,可能有进程切换,导致两个进程一起进入了临界区
(三) 双标志后检查法
-
设置一个布尔型数组flag[],flag[i]为true就代表进程i想要进入临界区.每个进程在进入临界区之前,先将flag[i]设置为true,再检测有没有别的进程想进入
-
漏洞:显而易见,若同时设为true,就会互相谦让,导致谁也进不了临界区
(四)Peterson算法
- 该算法可谓对双标志后检查法的改进,思想:若两个进程争夺进入,进程p0发现p1也想进入,就谦让给p1
也即’孔融让梨’的思想
3.临界区互斥的硬件层面
(一) 中断屏蔽
- 因为只有在发生中断的时候,cpu才会进行进程切换
- 因此进程互斥最简单的方法就是屏蔽中断,进而保证互斥的实现
(二) TestAndSet方法
- 执行TSL指令时,它的内部运转逻辑:
- 假设lock现在为false,代表临界资源A空闲,那么我就可以访问这个资源,同时将lock=true,提醒别的进程,这个临界资源A我正在使用,让他们等等
- 假设lock为true,代表临界资源正在有人使用,所以我必须等待,并且将lock=true,并不影响什么,所以没关系,只是为了让lock为false时可以上锁,将上锁与检查在一个TSL指令完成。
4.信号量机制
(一) 定义
为什么会出现信号量机制呢?回顾之前的软硬件互斥方法,软件方面上锁和加锁操作无法一气和成,硬件操作不法实现’让全等待’.信号量机制就是为了更高效的实现进程的同步以及互斥
- 所谓信号量,顾名思义就是用一个变量来代表系统资源的数量,如打印机,则信号量就为1(代表每次只有一个进程能使用打印机).
- 信号量利用一对原语wait()和singal()实现,通常简称为p,v操作
- 进程互斥软件层面上锁和加锁操作无法一气和成,而如果能把这些操作都利用原语实现,而原语又是由关中断/开中断实现的,因此,就能实现进程互斥.
(二) 整型信号量
- 用一个整数型的变量作为信号量,用来表示系统中的某个临界资源
int s=1;
void wait(int s){
while(s<=0);
s=s-1;
}
void signal(){
s++;
}
(三) 记录型信号量
- 记录型信号量是一个不会出现忙等现象的进程同步机制,除了一个整形外,还要有一个等待队列来保存相关进程
struct{
int value;
struct process *L; //等待队列
}
wait(semphare s){
s.value--;
if(s.value<0){
block(s.L)
}
}
wait函数的意思是: 若资源数小于0,说明以及没有空闲的资源可用了,此时需要利用Block原语将当前进程从运行态转化到阻塞态,让出cpu,并挂到对应资源的阻塞队列中去
这也是为什么记录型信号量可以避免忙等现象的原因
signal(){
s.value++:
if(s.value<0){
wake();
}
}
signal函数的意思是:释放一个资源后,若value<0,说明队列中还有进程在排队,因此需要利用wakeup原语,唤醒队列中的第一个进程
(四)利用信号量实现同步
设进程P2的语句y需要进程P1的语句X的执行结果作为前驱条件:
int semphare =0
p1{
....
X;
v(semphare)
}
p2{
----
p(semphare)
Y
}
(五) 利用信号量实现互斥
设临界资源每次只匀速一个进程访问:
int semphare =1
p1{
....
p(semphare)
X;
V(semphare)
}
p2{
....
p(semphare)
Y;
V(semphare)
}