----------软件实现方法:
在进入区设置和检查一些标志来表明是否有进程在临界区中,如果已有进程在临界区,则在进入区通过循环检查进行等待,进程离开临界区后则在退出区修改
标志。
(1)、算法一:单标志法。(违背“空闲让进”原则)
该算法设置一个公用整型变量 turn ,用于指示被允许进入临界区的进程编号,即,若turn = 0 ,则允许p0 进程进入临界区。该算法可确保每次只允许一个
进程进入临界区。但两个进程必须交换进入临界区,如果某个进程不再进入临界区了,那么另一个进程也将无法进入临界区(违背“空闲让进”)。这样很容易
造成资源利用不充分。
假如,p0顺利进入临界区并从临界区离开,那么此时临界区是空闲的,但p1 并没有进入临界区的打算,turn = 1 一直成立,p0 就无法再次进入临界区了
(一直被 while 死循环困住)
p0 进程:
while(turn != 0); //进入区
critical section ; //临界区
turn = 1; //退出区
remainder section; //剩余区
p1进程:
while(turn != 1); //进入区
critical section; //临界区
turn = 0; //退出区
remainder section; //剩余区
(2)、算法二:双标志法先检查。(违背“忙则等待”原则)
该算法的基本思想是在每一个进程访问临界区资源之前,先查看一下临界区资源是否正被访问,若正被访问,该进程需等待;否则,进程才进入自己
自己的临界区。为此,设置一个数据 flag[ i ] ,如第i个元素值为 FALSE ,表示Pi进程未进入临界区,值为 TRUE ,表示Pi进程进入临界区。
Pi 进程:
while(flag[ j ]); //进入区
flag[ i ] = TRUE; //进入区
critical section; //临界区
flag[ i ] = FALSE; //退出区
remainder section; //剩余区
Pj进程:
while(flag[ i ]); //进入区
flag[ j ] = TRUE; //进入区
critical section; //临界区
flag[ i ] = FALSE; //退出区
remainder section; //剩余区
优点:不用交替进入,可连续使用。
缺点:Pi 进程和 Pj进程可能同时进入临界区。
检查和修改操作不能一次进行。
(3)、算法三:双标志法后检查(会导致“饥饿”现象)
算法二是先检查对方进程状态标志后,再置自己的标志,由于在检查和放置中可插入另一个进程到达时的检测操作,会造成两个进程在分别检查
后,同时进入临界区。为此,算法三采用先设置自己标志为 TRUE 后,再检测对方状态标志,若对方标志位 TRUE,则进程等待;否则进入临界区。
Pi进程:
flag[ i ] =TRUE; //进入区
while(flag[ j ]); //进入区
critical section; //临界区
remainder section; //剩余区
Pj进程:
flag[ j ] =TRUE; //进入区
while(flag[ i ] ); //进入区
critical section; //临界区
remainder section; //剩余区
当两个进程几乎同时都想进入临界区时,它们分别将自己的标志值 flag 设置为 TRUE ,并且同时检测对方的状态(执行 while 语句),发现对方
也要进入临界区,于是双方互相谦让,结果谁也进不了临界区,从而导致“饥饿”现象。
(4)、算法四:Peterson's Algorithm(皮特森算法:单标志法和双标志法后检查的结合)
为了防止两个进程为进入临界区而无限期等待,又设置变量 turn ,,每个进程在设置自己标志后再设置 turn 标志。这时,再同时检测另一个进程
状态标志和不允许进入标志,这样可以保证当进程同时要求进入临界区,只允许一个进程进入临界区。
Pi 进程:
flag[ i ] = TRUE; turn = j; //进入区
while(flag[ j ] && turn == j); //进入区
critical section; //l临界区
flag[ i ] = FALSE ; //退出区
remainder section; //剩余区
Pj 进程:
flag[ j ] = TRUE; turn = i; //进入区
while(flag[ i ] && turn == i); //进入区
critical section; //临界区
flag[ j ] = FALSE; //退出区
remainder section; //剩余区
具体如下:考虑进程 Pi ,一旦它设置 flag[ i ] = TRUE ,表示它想要进入临界区,同时 turn = j ,此时如果进程 Pj 已经在临界区中,则符合进程Pi 的while
循环条件,则Pi 不能进入临界区。而如果 Pj 进程没要进入临界区,即 flag[ j ] = FALSE ,循环条件不符合,则 Pi 进程可以顺利进入,反之亦然,本算法
的基本思想是:算法一和算法三的结合。利用 flag 解决临界资源的互斥访问,而利用 turn 解决“饥饿”现象。
----------硬件实现方法:
计算机提供了特殊的硬件指令,允许对一个字中的内容进行检测和修正,或者是对两个字的内容进行交换等。通过硬件支持实现临界区问题的低级方法或
称为 元方法。
(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;
}
应为每个临界资源设置一个共享布尔变量 lock ,初值为 false;在每个进程中再设置一个局部布尔变量 key ,用于与 lock 变换信息。在进入临界区之前
先利用 Swap 指令交换 lock 与 key 的内容,然后检查 key 的状态;有进程在临界区时,重复交换和检查过程,直到进程退出。利用Swap 指令实现进程互斥的
算法描述如下:
key = true;
while(key != false);
Swap(&lock , &key);
进程的临界区代码段;
lock = false;
进程的其他代码;
硬件方法的优点:适用于任意数目的进程,不管是单处理机还是多处理机;简单、容易验证其正确性。可以支持进程内有多个临界区,只需为每个临界区
设立一个布尔变量。
硬件方法的缺点:进程等待进入临界区时要耗费处理时间,不能实现让权等待。从等待进程中随机选择一个进入临界区,有进程可能一直选不上,从而
导致“饥饿”现象。
注意:以上对 TestAndSet 和 Swap 指令的描述仅仅是功能实现,并非软件实现定义,事实上它们是由硬件逻辑直接实现的,不会被中断。
补充:以上的代码实现与我们平时在编译器上写的代码意义不同,以上的代码实现是为了表述进程实现同步和互斥的过程,并不是说计算机内部实现
同步互斥就是这些代码。
----------信号量:利用PV 操作实现互斥