操作系统4————进程同步

操作系统4————进程同步

一.目录

二.进程同步的基本概念

1. 同步机制的引入目的

在多道程序环境下,进程是并发执行的,不同进程之间存在着不同的相互制约关系。为了协调进程之间的相互制约关系,引入了进程同步的概念。

2. 临界资源

虽然多个进程可以共享系统中的各种资源,但其中许多资源一次只能为一个进程所使用,我们把一次仅允许一个进程使用的资源称为临界资源。许多物理设备都属于临界资源,如打印机等。此外,如果变量、数据等都可以被若干进程共享,也属于临界资源。

3. 临界区

对临界资源的访问,必须互斥地进行,在每个进程中,访问临界资源的那段代码称为临界区。

我们可以进入访问临界资源的代码分为4个区:

  • 进入区。为了进入临界区使用临界资源,在进入区要检查可否进入临界区,如果可以进入临界区,则应设置正在访问临界区的标志,以阻止其他进程同时进入临界区。
  • 临界区。进程中访问临界资源的那段代码,又称临界段。
  • 退出区。将正在访问临界区的标志清除。
  • 剩余区。代码中的其余部分。即:
    do {  
    entry section; //进入区  
    critical section; //临界区  
    exit section; //退出区  
    remainder section; //剩余区  
    } while (true)  

4. 两种形式的制约关系

b.直接相互制约关系(同步):直接相互制约关系是指,为了完成某个目而建立的两个或多个进程,这些进程需要在某些位置协调工作次序,信息传递而产生的制约关系,直接相互制约关系是由于进程间的相互合作而引起的。

比如说AB进程,A产生数据,B计算结果,AB公用一个缓存区。缓存区为空时,B不能运行,等待A向缓存区传递数据后B才能运行,缓存区满时,A不能运行,等待B取走数据后,A才能运行。此时AB为直接制约关系。

a.间接相互制约关系(异步):间接相互制约关系是指当一个进程进入临界区使用临界资源时,另一个进程必须等待,当占用临界资源的进程退出临界区后,另一进程才允许去访问此临界资源。

比如AB进程都需要使用打印机,如果进程A需要打印时, 系统已将打印机分配给进程B,则进程A必须阻塞。一旦进程B将打印机释放,系统便将进程A唤醒,并将其由阻塞状态变为就绪状态

5. 同步机制应遵循的规则

  • 空闲让进:当无进程处于临界区时,表明临界资源处于空闲状态,应允许一个请求进入临界区的进程立即进入自己的临界区,以有效利用临界资源。
  • 忙则等待:当已有进程进入临界区时,表明临界资源正在被访问,因而其他试图进入临界区的进程都必须等待,以保证对临界资源的互斥访问。
  • 有限等待:对要求访问的临界资源的进程,应保证在有限时间内能进入自己的临界区,以免陷入“死等”状态。
  • 让权等待:当进程不能进入自己的临界区时,应立即释放处理机,以免陷入”忙等”状态

三.软件同步机制

在进入区设置和检查一些标志来标明是否有进程在临界区,如果已有进程在临界区,则在进入区通过循环检查进行等待,进程离开临界区后则在退出区修改标志。

1. 算法一:单标志法。

思想: 该算法设置一个公用整型变量turn,用于指示被允许进入临界区的进程编号,比如turn = 0 ,则允许P0进程进入临界区。该算法可确保每次只允许一个进程进入临界区。

伪代码:

// P0进程
while(turn!=0);  // 进入区
critical section; //临界区
turn=1; //退出区
remainder section; //剩余区

// P1进程
while(turn!=1);  // 进入区
critical section; //临界区
turn=0; //退出区
remainder section; //剩余区

评价: 两个进程必须交替进入临界区,如果某个进程不再进入临界区了,那么另一个进程不再进入临界区(违背“空闲让进”)这样很容易造成资源利用的不充分。

2. 算法二:双标志法先检查

思想:该算法的基本思想是在每一个进程访问临界区资源之前,先查看一下临界资源是否正在被访问,若被访问,该进程需等待;否则,进程才进入自己的临界区。为此,设置了一个数据flag[i],如果第i个进程值为FLASE,则表示Pi进程未进临界区,值为TRUE,表示Pi进程进入临界区。

伪代码

//pi进程
while(flag[j]); //进入区  ① 
flag[i] = TRUE;//进入区 ③
critical section;//临界区
flag[i] = FALSE;//退出区
remainder section;//剩余区

//pj进程
white(flag[i]);//进入区 ②
flag[j] = TRUE //进入区  ④ 
critical section;//临界区
flag[j] = FLASE; //退出区
remainder section;//剩余区

评价: 优点:不要交替进入,可连续使用;缺点:pi和pj可能同时进入临界区,按序号①②③④执行,会同时进入临界区(违背”忙则等待“)。即在检查对方flag之后和切换自己的flag之间有一段时间,结果都检查通过。这里问题出在检查和修改操作不能一次进行。

3. 算法三:双标志法后检查

思想: 算法二先检测进程状态标志后,再置自己标志,由于在检测和放置中可插入另一个进程到达时的检测操作,会造成两个进程分别检测后。同时进入临界区。为此,算法三采用先设置自己标志为TRUE,再检测对方状态标志,若对方标志为TURE,则进程等待,否则进入临界区

伪代码:

// Pi进程  
flag[i] =TRUE;  
while(flag[j]);  
critical section;  
flag[i] =FLASE;  
remainder section;

// Pj进程  
flag[j] =TRUE;  // 进入区  
while(flag[i]);  // 进入区  
critical section;  // 临界区  
flag [j] =FLASE;   // 退出区  
remainder section;  // 剩余区 

评价: 当两个进程几乎同时想要进入临界区时,他们分别将自己的标志值flag设置为TRUE,并且同时检测对方的状态(执行while语句),发现对方也要进入临界区,预算对方互相谦让了,结果谁也进不了临界区,从而导致”饥饿”现象。

4. 算法四:Peterson’s Algorithm

思路: 为了防止两个进程为进入临界区而无限等待,又设置变量turn,指示不允许进入临界区的进程编号,每个进程在设置自己的标志后再设置turn标志,不允许另一个进程进入。这时,在同时检测另一个进程状态标志和不允许进入标志,这样可以保证两个进程同时要求进入临界区,只允许一个进程进入临界区

代码:

// Pi进程  
flag[i]=TURE; turn=j;  
while(flag[j]&&turn==j);   
critical section;  
flag[i]=FLASE;  
remainder section; 

// Pj进程  
flag[j] =TRUE;turn=i;  // 进入区  
while(flag[i]&&turn==i);   // 进入区  
critical section;  // 临界区  
flag[j]=FLASE;  // 退出区  
remainder section;  // 剩余区  

评价: 本算法的基本思想是算法一和算法三的结合。利用flag解决临界资源的互斥访问,而利用turn解决“饥饿”现象。

四.硬件同步机制

虽然可以利用软件方法解决诸进程互斥进入临界区的问题,但有一定难度,并且存在很大的局限性,因而现在已很少采用,相应的许多计算机已提供了一些特殊的硬件指令,允许对一个字中的内容进行检查和修改,或者是对两个字的内容进行交换等。

实际上,对临界区进行管理时,可以将标志看做一个锁,“锁开”进入,”锁关”等待,初始时,锁打开。每次进程要进入临界区时,检测锁。打开时,进入;关闭是,等待。

1. 关中断

思想: 在进入锁测试之前关闭中断,直到完成锁测试并上锁之后才能打开中断。这样,进程在临界区执行期间,计算机系统不响应中断,从而不会引发调度,也就不会打发生进程或者线程切换

缺点:
1. 滥用关中断权利可能会导致严重的后果
2. 关中断时间过长,会影响系统效率,限制了处理器交叉执行程序的能力;
3. 关中断方法也并不适用于多CPU系统,因为在一个处理机上关中断并不能防止其他进程在其他处理器上执行相同的临界段代码。

2. 利用Test-and-Set指令实现互斥

思想: 借助一条硬件指令——”测试并建立”指令TS以实现互斥的方法,TC指令是原子操作,即执行过程不可分割

TS指令描述如下:

boolean TS (boolean *lock){
    boolean old;
    old = *lock;
    *lock = TRUE;
    return old;
}

用TS指令管理临界区时,为每个临界资源设置一个布尔变量lock,lock初值为FALSE,表示该临界资源空闲。进程在进入临界区之前,首先用TS指令测试lock,如果其值为FALSE,则表示没有进程在临界区内,可以进入,并将TRUE值赋予lock,这等效于关闭临界资源.如果其值为TRUE,则重复检查,直到进程退出。

伪代码

do{
    ···
    while TS(&lock);
    critical seetion;
    lock = FALSE;
    remainder sectionl

}while(TRUE);

3. 利用Swap指令实现互斥

Swap指令称对换指令,在Intel 80*86中又称XCHG指令,用于交换两个字的内容。

Swap指令描述如下

 void swap (boolean *a,boolean *b){
     boolean temp;
     tamp = *a;
     *a = *b
     *b = temp;
 }

用Swap指令可以简单有效的实现互斥,方法是为每一个临界资源设置一个全局的boolean变量lock,其初值为false,在每个进程中再利用一个局部变量key。

伪代码

do{
    key = TRUE;
    do{
        Swap(&lock,&key);
    }while(key!=FALSE);

    临界区操作
    lock= FALSE;
}

五.信号量机制

信号量机制是一种功能性比较强的机制,可以用来解决互斥与同步的问题,它只能被两个标准的原语wait(S)和Signal(S)来访问,也可以记做“P操作”(通过)和“V操作”(释放)。wait和signal都属于原子操作。

1. 整形信号量

整形信号量就是用整形量S表示资源数目的多少。

P操作

wait(S){
    while(S<=0);
    S--;
}

v操作

signal(S){
    S++;
}

wait操作中,只要信号量S<=0,就会不断地测试。因此,该机制并未遵循“让权等待” 的准则,而是使进程处于“忙等”的状态。

2. 记录型信号量

记录型信号量是不存在“忙等”现象的进程同步机制。除了需要一个用于代表资源数目的整型变量value外,再增加一个进程链表L,用于链接所有等待该资源的进程,记录型信号量是由于釆用了记录型的数据结构得名。记录型信号量可描述为:

typedef struct{  
    int value; //资源数量  
    struct process *L;  //进程链表
} semaphore;

P操作

void wait (semaphore S) { //相当于申请资源  
    S.value--;  
    if(S.value<0) {  
        add this process to S.L;  
        block(S.L);  //,表示该类资源已分配完毕,因此进程应调用block原语,进行自我阻塞
    }  
} 

v操作

oid signal (semaphore S) {  //相当于释放资源
    S.value++;
    if(S.value<=0){
        remove a process P from S.L;
        wakeup(P);//将第一个等待进程唤醒。
    }
}

3. AND信号量

上述进程互斥问题都是针对一个临界资源而言的,在有些应用场合,一个进程需要同时获得两个或者更多的资源。AND信号量可以解决多临界资源申请问题。假设有S1,…Sn,N个资源,进程必须申请到所有资源后才可执行,则其wait 和signal描述为:
P操作

 void wait(S1, S2, ... , Sn){
    if (S1>=1 && S2>=1 && ... && Sn>=1 ) {
         for (int i=1; i<n; i++) 
             Si = Si - 1; 
    }
    else place this process//将当前进程放置在第一个不满足Si>=1的阻塞队列中 
}

v操作

void signal(S1, S2, ... , Sn){ 
    for (int i=1; i<n; i++) 
        Si = Si + 1;
     romove all the process waiting in the queue associated with Si into ready queue 
}

4. 信号量集

在记录型信号量机制中,wait和signal操作只能进行加一减一的操作。当需要一次性需要申请N个同类资源时,需要进行N次操作,这显然是低效的。为方便对资源的控制,每种资源在分配前需要检查其数量是否在其极限值之上。为此,对AND信号量进行扩充。S为信号量,d为需求量,t为下限值:
P操作

void wait(S1, d1, t1, S2, d2, t2, ... , Sn, dn, tn){ 
    if (S1>=t1 && S2>=t2 && ... && Sn>=tn ) 
        for (int i=1; i<n; i++) 
            Si = Si - dn; 
    else place this process//将当前进程放置在第一个不满足Si>=1的阻塞队列中 
}

v操作

void signal(S1, d1, S2, d2, ... , Sn, dn,){ 
    for (int i=1; i<n; i++) 
        Si = Si + dn;
       romove all the process waiting in the queue associated with Si into ready queue 
}

六.管程机制

1.引入管程的目的

虽然信号量机制是一种既方便又有效的同步方式,但每个要访问临界资源的进程都必须同时自备同步操作wait(S)和signal(S),这就使大量的同步操作分散在各个进程中。这样不仅给系统的管理者带来麻烦,而且还会因同步操作的使用不当而导致系统死锁。这样,在解决上述问题的过程中,便产生了新的进程同步工具———管程

2.管程的定义

系统中的各种硬件资源和软件资源,均可用数据结构抽象地描述其资源特性,即用少量信息和对资源所执行的操作来表征该资源,而忽略了它们的内部结构和实现细节。管程是由一组数据以及定义在这组数据之上的对这组数据的操作组成的软件模块,这组操作能初始化并改变管程中的数据和同步进程。

3.管程的特性

  1. 局部于管程的数据只能被局部于管程内的过程所访问。
  2. 一个进程只有通过调用管程内的过程才能进入管程访问共享数据。
  3. 每次仅允许一个进程在管程内执行某个内部过程。由于管程是一个语言成分,所以管程的互斥访问完全由编译程序在编译时自动添加,无需程序员关注,而且保证正确。

4.管程的组成

  1. 局部于管程的共享结构数据说明。
  2. 对该数据结构进行操作的一组过程。
  3. 对局部于管程的共享数据设置初始值的语句。

七.参考资料

进程同步(操作系统)
《操作系统 第四版》

猜你喜欢

转载自blog.csdn.net/qq_38499859/article/details/80217518