OS知识点汇总(考研用)——第二章:进程管理(下)
本文参考于《2021年操作系统考研复习指导》(王道考研),《计算机操作系统教程》
思维导图:
2.进程管理
2.3 进程同步
2.3.1 进程同步的基本概念
在多道程序环境下,进程是并发执行的,不同进程之间存在着不同的相互制约关系。为了协调进程之间的相互制约关系,引入了进程同步的概念。例如,让系统计算1+2x3,假设系统产生两个进程:一个是加法进程,一个是乘法进程。要让计算结果正确,要让加法进程发生在乘法进程之后,但实际上OS具有异步性,若不加以制约,加法进程发生在乘法进程之前是绝对有可能的,因此要制定一定的机制去约束加法进程,让它在乘法进程完成之后才发生。
1.临界资源
虽然多个进程可以共享系统中的各种资源,但其中许多资源一次只能为一个进程所用,将一次仅允许一个进程使用的资源称为临界资源。 许多物理设备都属于临界资源,如打印机。此外,还有许多变量、数据等都可以被若干进程共享,也属于临界资源。
对临界资源的访问,必须互斥地进行,在每个进程中,访问临界资源的那段代码称为临界区。 为了保证临界资源的正确使用,可把临界资源的访问过程分成4个部分:
(1)进入区
为了进入临界区使用临界资源,在进入区要检查可否进入临界区,若能进入临界区,则应设置正在访问临界区的标志,以阻止其他进程同时进入临界区
(2)临界区
进程中访问临界资源的那段代码,又称临界段
(3)退出区
将正在访问临界区的标志清除
(4)剩余区
代码中的其余部分
do{
entry section; //进入区
critical section; //临界区
exit section; //退出区
remainder section; //剩余区
}while(true)
2.同步
同步亦称直接制约关系,是指为完成某种任务而建立的两个或多个进程,这些进程因为需要在某些位置上协调它们的工作次序而等待、传递信息所产生的制约关系。 进程间的直接制约关系源于它们之间的相互合作。
例如,输入进程A通过单缓冲向进程B提供数据。当该缓冲区为空时,进程B不能获得所需数据而阻塞,一旦进程A将数据送入缓冲区,进程B就被唤醒。反之,当缓冲区满时,进程A被阻塞,仅当进程B取走缓冲数据时,才唤醒进程A。
例题:在OS中,要对并发进程进行同步的原因是()
A.进程必须在有限的时间内完成
B.进程具有动态性
C.并发进程是异步的
D.进程具有结构性
答案:C;
3.互斥
互斥也称间接制约关系。当一个进程进入临界区使用临界资源时,另一个进程必须等待,当占用临界资源的进程退出临界区后,另一进程才允许去访问此临界资源。
例如,在仅有一台打印机的系统中,有两个进程A和进程B,若进程A需要打印时,系统已将打印机分配给进程B,则进程A必须阻塞。一旦进程B将打印机释放,系统便将进程A唤醒,并将其由阻塞态变为就绪态。
为禁止两个进程同时进入临界区,同步机制应遵循以下准则:
(1)空闲让进
临界区空闲时,可以允许一个请求进入临界区的进程立即进入临界区
(2)忙则等待
当已有进程进入临界区时,其他试图进入临界区的进程必须等待
(3)有限等待
对请求访问的进程,应保证能在有限时间内进入临界区
(4)让权等待
当进程不能进入临界区时,应立即释放处理器,防止进程忙等待
例题:进程之间存在哪几种制约关系?各是什么原因引起的?以下活动各属于哪种制约关系
(1)若干学生去图书馆借书
(2)两队进行篮球比赛
(3)流水线生产的各道工序
(4)商品生产和消费
答案:进程之间存在两种制约关系,即同步与互斥
同步是由于并发进程之间需要协调完成同一个任务时引起的一种关系,是一个进程等待另一个进程向它直接发送消息或数据时的一种制约关系
互斥是由并发进程之间竞争系统的临界资源引起的,是一个进程等待另一个进程已经占有的必须互斥使用的资源时的一种制约关系
(1)互斥,同一本书只能被一名学生借阅,或任何时刻只能有一名学生借阅一本书
(2)互斥,篮球是互斥资源,只可被一个队获得
(3)同步,一个工序完成后开始下一个工序
(4)同步,生产商品后才能消费
例题:有两个并发进程P1、P2,其程序代码如下:
P1() P2()
{
{
x=1; x=-1;
y=2; a=x+3;
if(x>0) x=a+x;
z=x+y; b=a+x;
else c=b*b;
z=x*y; print c;
print z; }
}
(1)可能打印出的z值有多少(假设每条赋值语句是一个原子操作)
(2)可能打印出的c值有多少(其中x为P1和P2的共享变量)
答案:(1)-2,1,2,3,5,7;(2)9,25,81
2.3.2 实现临界区互斥的基本方法
1.软件实现方法
在进入区设置并检查一些标志来标明是否有进程在临界区中,若已有进程在临界区,则在进入区通过循环检查进行等待,进程离开临界区后则在退出区修改标志。
(1)算法一:单标志法
P0进程: P1进程:
while( turn!=0 ); while( turn!=1 );
critical section; critical section;
turn=1; turn=0;
remainder section; remainder section;
该算法设置一个公用整型变量turn,用于指示被允许进入临界区的进程编号,即若turn=0,则允许P0进程进入临界区。该算法可确保每次只允许一个进程进入临界区。但两个进程必须交替进入临界区,若某个进程不再进入临界区,则另一个进程也将无法进入临界区(违背“空闲让进”)。 若P0顺利进入临界区并从临界区离开,则此时临界区是空闲的,但P1并没有进入临界区的打算,turn=1一直成立,P0就无法再次进入临界区
(2)算法二:双标志法先检查
Pi进程: Pj进程:
while( flag[j] ); /1 while( flag[i] ); /2
flag[i]=TRUE; /3 flag[j]=TRUE; /4
critical section; critical section;
flag[i]=FALSE; flag[j]=FALSE;
remainder section; remainder section;
该算法的基本思想是在每个进程访问临界区资源之前,先查看临界资源是否正在被访问,若正被访问,该进程需等待;否则,进程才进入自己的临界区。 为此,设置一个数据flag[i],如第i个元素值为FALSE,表示Pi进程未进入临界区,值为TRUE,表示Pi进程进入临界区。
优点:不用交替进入,可连续使用;
缺点:Pi和Pj可能同时进入临界区,按序列1,2,3,4执行时,会同时进入临界区(违背“忙则等待”)。即在检查对方的flag后和切换自己的flag前有一段时间,结果都检查通过。这里的问题出在检查和修改操作不能一次进行。
(3)算法三:双标志法后检查
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;
算法二先检测对方的进程状态标志,再置自己的标志,由于在检测和放置中可插入另一个进程到达时的检测操作,会造成两个进程在分别检测后同时进入临界区。为此,算法三先将自己的标志设置为TRUE,再检测对方的状态标志,若对方标志为TRUE,则进程等待;否则进入临界区。
两个进程几乎同时都想进入临界区时,它们分别将自己的标志值flag设置为TRUE,并且同时检测对方的状态,发现对方也要进入临界区时,双方互相谦让,结果谁也进不了临界区,从而导致饥饿现象。
(4)算法四:Peterson’s Algorithm
Pi进程: Pj进程:
flag[i]=TRUE; flag[j]=TRUE;
turn=j; 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;
为了防止两个进程为进入临界区而无限期等待,又设置了变量turn,每个进程在先设置自己的标志后再设置turn标志。这时,再同时检测另一个进程状态标志和不允许进入标志,以便保证两个进程同时要求进入临界区时,只允许一个进程进入临界区。
具体如下:考虑进程Pi,一旦设置flag[i]=true,就表示它想要进入临界区,同时turn=j,此时若进程Pj已在临界区中,符合进程Pi中的while循环条件,则Pi不能进入临界区。若Pj不想要进入临界区,即flag[j]=false,循环条件不符合,则Pi可以顺利进入,反之亦然。本算法的基本思想是算法一和算法三的结合。 利用flag解决临界资源的互斥访问,而利用turn解决饥饿现象 (若两个进程几乎同时都想进入临界区,最后turn的值要么是i,要么是j,而两个flag都为TRUE,此时总有一个进程可以执行,而另一个进程不能执行,克服了算法三中的饥饿现象)
Peterson’s Algorithm可类比为两个人进门,每个人进门前都会和对方客套一句“你先走”。如果进门时没别人,就当和空气说句废话,然后大步登门入室;如果两个人同时进门,就互相请先,但各自只客套一次,所以先客套的人请完对方,就等着对方请自己,然后光明正大地进门
例题:
答案:
2.硬件实现方法
计算机提供了特殊的硬件指令,允许对一个字中的内容进行检测和修正,或对两个字的内容进行交换等。 通过硬件支持实现临界段问题的方法称为低级方法,或称元方法。
(1)中断屏蔽方法
.
.
.
关中断;
临界区;
开中断;
.
.
.
当一个进程正在使用处理机执行它的临界区代码时,防止其他进程进入其临界区进行访问的最简方法是,禁止一切中断发生,或称之为屏蔽中断,关中断。 因为CPU只在发生中断时引起进程切换,因此屏蔽中断能够保证当前运行的进程让临界区代码顺利地执行完,进而保证互斥的正确实现,然后执行开中断。
这种方法限制了处理机交替执行程序的能力,因此执行的效率会明显降低。对内核来说,在它执行更新变量或列表的几条指令期间,关中断是很方便的,但将关中断的权力交给用户则很不明智,若一个进程关中断后不再开中断,则系统可能会因此终止。
(2)硬件指令方法
TestAndSet指令:这条指令是原子操作,即执行该代码时不允许被中断。其功能是读出指定标志后把该标志设置为真。 指令的功能描述如下:
boolean TestAndSet(boolean *lock)
{
boolean old;
old=*lock;
*lock=true;
return old;
}
可为每个临界资源设置一个共享布尔变量lock,表示资源的两种状态:true表示正被占用,初值为false。在进程访问临界资源之前,利用TestAndSet检查和修改标志lock;若有进程在临界区,则重复检查,直到进程退出。 利用该指令实现进程互斥的算法描述如下:
while TestAndSet(&lock);
进程的临界区代码段;
lock=false;
进程的其他代码;
Swap指令: 该指令的功能是交换两个字(字节)的内容。其功能描述如下:
Swap(boolean *a,boolean *b)
{
boolean temp;
temp=*a;
*a=*b;
*b=temp;
}
对TestAndSet和Swap指令的描述仅是功能实现,而并非软件实现的定义,事实上,它们是由硬件逻辑直接实现的,不会被中断。
应为每个临界资源设置一个共享布尔变量lock,初值为false;在每个进程中再设置一个局部布尔变量key,用于与lock交换信息。在进入临界区前,先利用Swap指令交换lock与key的内容,然后检查key的状态;有进程在临界区时,重复交换和检查过程,直到进程退出。 利用Swap指令实现进程互斥的算法描述如下:
key=true;
while( key!=false )
Swap(&lock,&key);
进程的临界区代码段;
lock=false;
进程的其他代码;
硬件方法的优点:适用于任意数目的进程,而不管是单处理机还是多处理机;简单、容易验证其正确性。可以支持进程内有多个临界区,只需为每个临界区设立一个布尔变量。
硬件方法的缺点:进程等待进入临界区时要耗费处理机时间,不能实现让权等待。从等待进程中随机选择一个进入临界区,有的进程可能一直选不上,从而导致饥饿现象。
以上的所有代码实现只是为了表述进程实现同步和互斥的过程,并不是说计算机内部实现同步互斥的就是这些代码
2.3.3 信号量
信号量机制是一种功能较强的机制,可用来解决互斥与同步问题,它只能被两个标准的原语wait(S)和signal(S)访问,也可记为P操作和V操作。
原语是指完成某种功能且不被分割、不被中断执行的操作序列,通常可由硬件来实现。 例如,前述的TestAndSet和Swap指令就是由硬件实现的原子操作。原语功能的不被中断执行特性在单处理机上可由软件通过屏蔽中断方法实现。
原语之所以不能被中断执行,是因为原语对变量的操作过程若被打断,可能会去运行另一个对同一变量的操作过程,从而出现临界段问题。若能够找到一种解决临界段问题的元方法,就可实现对共享变量操作的原子性。
1.整型信号量
整型信号量被定义为一个用于表示资源数目的整型量S,wait和signal操作可描述为:
wait(S)
{
while( S<=0 );
S=S-1;
}
signal(S)
{
S=S+1;
}
wait操作中,只要信号量S<=0,就会不断地测试。因此,该机制并未遵循让权等待的准则,而是使进程处于忙等的状态。
2.记录型信号量
记录型信号量是不存在忙等现象的进程同步机制。除需要一个用于代表资源数目的整型变量value外,再增加一个进程链表L,用于链接所有等待该资源的进程。 记录型信号量得名于采用了记录型的数据结构。记录型信号量可描述为:
typedef struct
{
int value;
struct process *L;
}semaphore;
相应的wait(S)和signal(S)的操作如下:
void wait( semaphore S ) //相当于申请资源
{
S.value--;
if(S.value<0)
{
add this process to S.L;
block(S.L);
}
}
wait操作,S.value- -表示进程请求一个该类资源,当S.value<0时,表示该类资源已分配完毕,因此进程应调用block原语,进行自我阻塞,放弃处理机,并插入该类资源的等待队列S.L,可见该机制遵循了让权等待的准则。
void signal( semaphore S ) //相当于释放资源
{
S.value++;
if( S.value<=0 )
{
remove a process P from S.L;
wakeup( P );
}
}
signal操作,表示进程释放一个资源,使系统中可供分配的该类资源数增1,因此有S.value++。若加1后仍是S.value<=0,则表示在S.L中仍有等待该资源的进程被阻塞,因此还应调用wakeup原语,将S.L中的第一个等待进程唤醒。
例题:一个进程因在互斥信号量mutex上执行V(mutex)操作而导致唤醒另一个进程时,执行V操作后mutex的值为()
A.大于0 B.小于0
C.大于等于0 D小于等于0
答案:D;
3.利用信号量实现同步
信号量机制能用于解决进程间的各种同步问题。设S为实现进程P1,P2同步的公共信号量,初值为0。进程P2中的语句y要使用进程P1中语句x的运行结果,所以只有当语句x执行完成之后语句y才可以执行。
semaphore S=0; //初始化信号量
P1()
{
x; //语句x
V(S); //告诉进程P2,语句x已经完成
...
}
P2()
{
...
P(S); //检查语句x是否运行完成
y; //检查无误,运行y语句
...
}
若P2先执行到P(S)时,S为0,执行P操作会把进程P2阻塞,并放入阻塞队列;当进程P1中的x执行完后,执行V操作,把P2从阻塞队列中放回就绪队列,当P2得到处理机时,就得以继续执行
4.利用信号量实现进程互斥
设S为实现进程P1,P2互斥的信号量,由于每次只允许一个进程进入临界区,所以S的初值应该为1(即可用资源数为1)。 只需把临界区置于P(S)和V(S)之间,即可实现两个进程对临界资源的互斥访问。
semaphore S=1; //初始化信号量
P1()
{
...
P( S ); //准备开始访问临界资源,加锁
进程P1的临界区;
V( S ); //访问结束,解锁
...
}
P2()
{
...
P( S ); //准备开始访问临界资源,加锁
进程P2的临界区;
V( S ); //访问结束,解锁
...
}
当没有进程在临界区时,任意一个进程要进入临界区,就要执行P操作,把S的值减为0,然后进入临界区;当有进程存在于临界区时,S的值为0,再有进程要进入临界区,执行P操作时将会被阻塞,直至在临界区中的进程退出,这样便实现了临界区的互斥。
互斥是不同进程对同一信号量进行P,V操作实现的,一个进程成功对信号量进行了P操作后进入临界区,并在退出临界区后,由该进程本身对该信号量执行V操作,表示当前没有进程进入临界区,可以让其他进程进入。
同步互斥中的P,V操作:在同步问题中,若某个行为要用到某种资源,则在这个行为前面P这种资源一下;若某个行为会提供某种资源,则在这个行为后面V这种资源一下。在互斥问题中,P,V操作要紧夹使用互斥资源的那个行为,中间不能有其他冗余代码。
5.利用信号量实现前驱关系
信号量也可用来描述程序之间或语句之间的前驱关系。
S1,S2,S3,…,S6是最简单的程序段(只有一条语句)。为使各程序段能正确执行,应设置若干初始值为0的信号量。为保证S1——>S2,S1——>S3的前驱关系,应分别设置信号量a1,a2,为保证S2——>S4,S2——>S5,S3——>S6,S4——>S6,S5——>S6,应设置信号量b1,b2,c,d,e
semaphore a1=a2=b1=b2=c=d=e=0; //初始化信号量
S1()
{
...;
V(a1); //S1已经运行完成
V(a2);
}
S2()
{
P(a1); //检查S1是否运行完成
...;
V(b1); //S2已经运行完成
V(b2);
}
S3()
{
P(a2); //检查S1是否运行完成
...;
V(c); //S3已运行完成
}
S4()
{
P(b1); //检查S2是否已经运行完成
...;
V(d); //S4已经运行完成
}
S5()
{
P(b2); //检查S2是否已经运行完成
...;
V(e); //S5已经运行完成
}
S6()
{
P(c); //检查S3是否已经运行完成
P(d); //检查S4是否已经运行完成
P(e); //检查S5是否已经运行完成
...;
}
例题:
答案:
P1()
{
P(S1);
从输入设备输入数据a;
V(S2);
P(Sb