进程互斥与进程同步
多个并发的进程存在两种类型的关系:无关的和相关的,无关进程各自执行但不会影响到其他的进程,相关的进程因为进程之间直接和间接的制约关系导致进程执行的时候因为不确定性使得得到的结果具有不可预知性
相关的进程并发执行后具有不可再现性,这是因为并发执行的进程之间的两种制约关系:直接制约关系,间接制约关系
直接制约关系:因为进程直接的执行具有先后关系,即需要的执行的进程和其他进程之间需要相互合作,才能正确执行进程,这也导致了进程之间的直接制约
间接制约关系:因为执行进程是需要资源的,而资源有限,这也导致了需要使用同一资源的进程之间具有制约关系,这也称为间接制约关系
而因为资源共享的普遍存在,因此资源之间的竞争的间接关系是一种最简单、最基本的制约关系
理解资源共享和导致的间接关系先从资源的使用步骤来看,用户程序使用资源的步骤是申请,使用,归还,但因为有些资源只能一次被一个进程使用,因此在申请资源时就有了先后问题,在使用资源时其他的进程必须排队
这种一次只能被一个进程使用的资源称为临界资源,而进程对应的程序在访问临界资源时的一段程序代码又称为临界区,就是进程在资源的一次使用过程中从申请开始至归还为止的一段程序代码
也就是说进程在临界区中执行的时候其他要进入临界区执行的进程必须等待,不允许处理器在临界区内轮流交替的执行,这也称为互斥执行
而具有一组互斥关系的进程称为互斥进程
详说直接制约关系
直接制约关系具有几种分类:单向依赖关系,互相依赖关系,同步关系
单向依赖关系:进程B执行之前必须执行进程A
相互依赖关系:进程A和进程B相互依赖,如进程A中的L1指令的执行依赖于进程B中的L4指令的执行,而进程B中的L3指令的执行依赖于进程A中L1指令的执行,而若是进程A的第一次执行不受限制,那么可以反复的执行进程A和进程B中的指令,这就是相互依赖关系
同步关系:具有单向依赖关系或者相互依赖关系的进程就是同步的关系,也称为同步进程
引入制约关系的用处
因为相关进程的正确执行需要按照一定的顺序才能保证确定的执行结果,而这些依赖关系则保证的进程的正确顺序的执行,因此有了根据这些关系执行进程的机制,这个机制称为进程同步机制
常用的进程同步机制:加锁机制、标志位机制、信号量机制、管程机制
加锁机制
因为进程之间具有互斥关系,因此用加锁机制来控制进程的执行就是对进程互斥关系的实现,也就是说因为临界区一次只能执行一个进程,通过控制进程进入临界区来控制进程的执行的顺序,也就是对临界区执行的控制,其他的进程同步机制也是由此来控制进程的执行顺序
先来讲讲临界区管理的四个准则:空闲让进,忙则等待,有限等待,让权等待
空闲让进是指临界区没有进程执行时就让其他进程进入,忙则等待是指临界区中有进程执行是其他的想要进入临界区的进程要排队等待,有限等待是指要进入临界区的进程在等待有限的时间之后就能够进入临界区,让权等待是指进程在离开临界区之后就会让其他的进程进入临界区
加锁机制原理
加锁机制顾名思义就是在进程进入临界区之后在临界区的入口加锁使得其他要进入临界区的进程不能进入临界区,在进程离开临界区的时候才解锁
加锁机制需要用到的几个内容:锁变量key,加锁操作lock(key),解锁操作unlock(key)
锁变量key
就和钥匙一样,用来开锁的一个变量,锁变量取0的时候能开锁,此时临界资源是空闲的,进程可以进入临界区执行,锁变量取1的时候不能开锁,此时临界资源已经被某个进程占用,不能进入临界区执行
加锁操作lock(key)
加锁操作的定义如下:
lock(key)
{
while(key==1);
key=1;
}
如上定义,如果说锁变量key为1,那么进入lock之后将会执行while语句,一直执行下去,无法执行下一条指令,也就是无法使得进程进入临界区,如果说锁变量key为0,那么进入lock之后将不会执行while语句,key变为1,执行下一条指令,进程进入临界区,而由于key变成了1,使得其他的进程无法进入临界区
解锁操作unlock(key)
解锁操作的定义如下:
unlock(key)
{
key=0;
}
当进程从临界区出来的时候执行解锁操作,key由1变成0,则下一个进程就能进入临界区了
普通加锁机制的问题
假如说有A,B两个进程,在执行进程A中的加锁操作lock(key)中的while语句后直接跳转执行进程B,而此时key仍然为0,因此进程B进入临界区而且加上了锁key变成了1,但此时若再回到进程A中恢复现场,直接执行了while语句后的key=1语句,进程A也加上锁进入临界区,此时进程之间的执行就出错了,因此为了改进这种错误,可以借助硬件实现加锁操作,就不会存在这种问题
加锁机制分析(互斥关系)
一、普通的加锁机制容易出问题,不能实现互斥关系,因此需要借助硬件的加锁机制才能实现进程的互斥关系
二、存在“忙等待”现象,浪费了处理器的时间
什么意思呢,就是说因为while语句的循环是持续不断的,只有等待其他的进程出了临界区的时候while语句才会停止,这就使得处理器虽然被分配给这个进程,但是因为不断的执行while循环导致处理器被浪费了
三、存在“饥饿”现象
比如进程A在执行while循环,进程B在临界区中执行,当进程B出来之后可能并不执行进程A而是去执行进程C,以此类推,某些进程可能永远得不到执行而一直处于循环之中,就像不能吃饭一样,因此叫做饿死现象
四、多个锁变量加锁操作可能造成进程死锁
信号量机制(互斥关系)
简单的说就是利用变量—信号量的值(大于0或者小于0或者等于0)来作为判定条件,通过增减信号量的值来控制进程的唤醒,执行,和堵塞
信号量机制原理
三个定义:信号量,P操作,V操作
信号量
一种变量,一个信号量有整型变量value和等待队列bq两部分组成,信号量数据类型简化定义如下
struct semaphore{
int value;
PCB *bq;
}
P操作
通俗的讲就是执行P操作来判定是否将该进程放入临界区,具体定义如下
P(s)
{
s.value=s.value-1;
if(s.value<0) blocked(s);
}
这里block(s)是阻塞原语,将当前p(s)操作的进程设置为阻塞状态并加入到信号量s对应的等待队列,这里不好理解,下面会举例分析
V操作
就是进程从临界区出来的时候会执行的操作,定义如下
V(s)
{
s.value=s.value+1;
if(s.value<=0) wakeup(s);
}
这里wakeup(s)是唤醒语句,就是从信号量s对应的等待队列bq中唤醒一个进程,也就是从等待队列bq中选择一个进程将其转化为就绪状态
在信号量机制中p和v操作都被定义为原语,即不可被打断
PV操作中的s是互斥变量,初始值为1
下面关于信号量机制的分析
s.value一开始是1,那么执行P操作的时候,s.value--,变成了0,那么不执行P操作中的if语句,即执行P操作所在的进程,而在执行完进程之后执行V操作,因为s.value++,s.value变成了1,显然就又回到了原本的s.value的值
加如有几个进程A,B,C...在执行完进程A的P操作,然后再去执行进程B的P操作,此时s.value的值经过s.value--,s.value--此时其值为-1,则执行进程B中的if语句,将进程B变为阻塞状态加入到信号s对应的等待队列中,等到进程A执行V操作时由于s.value<=0,则执行V操作中的if语句,从而唤醒等待队列中的一个进程
如此实现了进程之间的互斥执行
接下来讲一讲信号量机制和同步关系
从上文可以知道同步关系是由单向依赖关系和相互依赖关系,其中单向依赖关系又称为简单同步关系,相互依赖关系又称为一般同步关系
信号量机制和简单同步关系
先假设由两个进程A,B,其中进程A单向依赖于进程B,此时进程A仅有P操作,进程B仅有V操作,我们将s的初值定义为0,即s.value=0,那么在执行进程A中的P操作的时候,因为s.value=0则进程A阻塞进入等待队列而执行进程B,而因为此时s.value=-1则执行进程B中的if语句唤醒进程A,从而两进程之间符合单向依赖关系
若是进程B先执行,那么此时s.value=0执行完进程B的V操作之后s.value=1就能直接执行进程A了
信号量机制和一般同步关系
因为有两个依赖关系,所以定义两个信号量s1=1和s2=0,则进程A的L1指令中有P(s1)和L2指令中有V(s2)进程B有L3指令中有P(s2)和L4指令中有V(s1),则进程A一开始不需要条件即可执行,即s1=1时执行进程A中的L1指令则s1变成0,待执行到进程A的L2指令中V操作时由于s2为0,则s2变成1,从而执行进程B中的L3指令时s2为1,则可执行,执行到进程B中的L4指令是s1由0变成1,关系图如下,读者自行领会即可
进程A 进程B
L1:P(s1) L3:P(s2)
L2:P(s2) L4:P(s1)
《计算机操作系统原理分析》笔记