操作系统王道考研复习——第二章(进程管理) 下

2.4 进程同步

在OS中引入进程后,一方面可以使系统中的多道程序并发执行,这不仅能有效地改善资源利用率,还可显著地提高系统的吞吐量,但一方面却使系统变得更加复杂。如果不能采取有效的措施,对多个进程的运行进行妥善的管理,必然会因为执行进程对系统资源的无序争夺给系统造成混乱。致使每次处理的结果存在着不确定性,即显示出其不可再现性。即进程具有异步性的特征。异步性是指,各并发执行的进程以各自独立的、不可预知的速度向前推进。

为保证多个进程能有条不紊地运行,在多道程序系统中,必须引入进程同步机制。接下来会介绍单处理机系统中的进程同步机制——硬件同步机制信号量机制管程机制等,利用它们来保证程序执行的可再现性。

2.4.1 进程同步的基本概念

进程同步机制的主要任务,是对多个相关进程在执行次序上进行协调,使并发执行的诸进程之间能按照一定的规则(或时序)共享系统资源,并能很好地相互合作,从而使程序的执行具有可再现性

1. 进程同步

同步亦称直接制约关系,是指为完成某种任务而建立的两个或多个进程,这些进程因为需要在某些位置上协调它们的工作次序而产生的制约关系。进程间的直接制约关系源于它们之间的相互合作
在这里插入图片描述
例如,读进程和写进程并发运行,由于并发必然导致异步性,因此读进程和写进程两个操作的执行顺序是不确定的。而实际应用中,又必须按照“写进程→读进程”的顺序来执行。因为它们之间的相互合作,所以需要去协调它们的工作次序。

2. 进程互斥

互斥又称间接制约关系。进程互斥指当一个进程访问某临界资源时,另一个想要访问该临界资源的进程必须等待。当前访问临界资源的进程访问结束,释放该资源之后,另一个进程才能去访问临界资源。
在这里插入图片描述
例如,在仅有一台打印机(需要互斥访问)的系统中,有两个进程A和进程B,若进程A需要打印时,系统已将打印机分配给进程B,则进程A必须阻塞。一旦进程B将打印机释放,系统便将进程A唤醒,并将其由阻塞态变为就绪态。

3. 临界资源

我们把一个时间段内只允许一个进程使用的资源称为临界资源。许多物理设备(比如摄像头、打印机)都属于临界资源。此外还有许多变量、数据、内存缓冲区等都属于临界资源。

对临界资源的访问,必须互斥地进行,在每个进程中,访问临界资源的那段代码称为临界区,也称为临界段。为了保证临界资源的正确使用,可把临界资源的访问过程分成4个部分:
在这里插入图片描述
注意:
临界区是进程中访问临界资源的代码段。
进入区和退出区是负责实现互斥的代码段。

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

为了实现对临界资源的互斥访问,同时保证系统整体性能,需要遵循以下原则:

  1. 空闲让进。临界区空闲时,可以允许一个请求进入临界区的进程立即进入临界区;
  2. 忙则等待。当已有进程进入临界区时,其他试图进入临界区的进程必须等待;
  3. 有限等待。对请求访问的进程,应保证能在有限时间内进入临界区(保证不会饥饿);
  4. 让权等待。当进程不能进入临界区时,应立即释放处理机,防止进程忙等待。

在这里插入图片描述

2.4.2 实现临界区互斥的基本方法

如果没有注意进程互斥的话,将会出现以下现象:
在这里插入图片描述
先调度A上处理机运行,当A在使用打印机的过程中,分配给它的时间片用完了,接下来操作系统调度B让它上处理机运行,进程B也在使用打印机。
结局:A、B 的打印内容混在一起了。

而想要实现互斥可以看一下下面介绍的方法。

1. 软件实现方法
1)单标志法

算法思想:两个进程在访问完临界区后会把使用临界区的权限转交给另一个进程。也就是说每个进程进入临界区的权限只能被另一个进程赋予
在这里插入图片描述
turn 的初值为 0,即刚开始只允许 0 号进程进入临界区。
若 P1 先上处理机运行,则会一直卡在 ⑤。直到 P1 的时间片用完,发生调度,切换 P0 上处理机运行。代码 ① 不会卡住 P0,P0 可以正常访问临界区,在 P0 访问临界区期间即时切换回 P1,P1依然会卡在 ⑤。只有 P0 在退出区将 turn 改为 1 后,P1才能进入临界区。
因此,该算法可以实现“同一时刻最多只允许一个进程访问临界区”

类比一下生活中的例子:
在这里插入图片描述
而这种算法只能按 P0 → P1 → P0 → P1 →……这样轮流访问。这种必须“轮流访问”带来的问题是,如果此时允许进入临界区的进程是 P0,而 P0 一直不访问临界区,那么虽然此时临界区空闲,但是并不允许 P1 访问。
因此,单标志法存在的主要问题是:违背“空闲让进”原则

2)双标志先检查法

算法思想:设置一个布尔型数组 flag[],数组中各个元素用来标记各进程想进入临界区的意愿,比如 “flag[0] = ture”意味着 0 号进程 P0 现在想要进入临界区。每个进程在进入临界区之前先检查当前有没有别的进程想进入临界区,如果没有,则把自身对应的标志 flag[i] 设为 true,之后开始访问临界区。
在这里插入图片描述
在这里插入图片描述
若按照 ①⑤②⑥③⑦….的顺序执行,P0 和 P1 将会同时访问临界区。
因此,双标志先检查法的主要问题是:违反“忙则等待”原则。不能实现对临界资源的互斥访问。
原因在于,进入区的“检查”和“上锁” 两个处理不是一气呵成的。“检查”后,“上锁”前可能发生进程切换。

3)双标志后检查法

算法思想:双标志先检查法的改版。前一个算法的问题是先“检查”后“上锁”,但是这两个操作又无法一气呵成,因此导致了两个进程同时进入临界区的问题。因此,人们又想到先“上锁”后“检查”的方法,来避免上述问题。
在这里插入图片描述
在这里插入图片描述
若按照 ①⑤②⑥….的顺序执行,P0 和 P1 将都无法进入临界区。
因此,双标志后检查法虽然解决了“忙则等待”的问题,但是又违背了“空闲让进”和“有限等待”原则,会因各进程都长期无法访问临界资源而产生“饥饿”现象。
两个进程都争着想进入临界区,但是谁也不让谁,最后谁都无法进入临界区,就发生了死锁

4)Peterson算法

算法思想:结合双标志法、单标志法的思想。如果双方都争着想进入临界区,那可以让进程尝试“孔融让梨”(谦让)。做一个有礼貌的进程。
在这里插入图片描述
在这里插入图片描述
Peterson 算法用软件方法解决了进程互斥问题,遵循了空闲让进、忙则等待、有限等待三个原则,但是依然未遵循让权等待的原则。
Peterson 算法相较于之前三种软件解决方案来说,是最好的,但依然不够好。

2. 硬件实现方法

通过硬件支持实现临界段问题的方法称为低级方法,或称元方法。

1)中断屏蔽方法

利用“开/关中断指令”实现(与原语的实现思想相同,即在某进程开始访问临界区到结束访问为止都不允许被中断,也就不能发生进程切换,因此也不可能发生两个同时访问临界区的情况)。
在这里插入图片描述
优点:简单、高效。
缺点:不适用于多处理机(因为一个处理机的关中断指令对另外一个处理机并不影响,所以有可能多个处理机同时进入临界区的情况),只适用单处理机;只适用于操作系统内核进程,不适用于用户进程(因为开/关中断指令只能运行在内核态,这组指令如果能让用户随意使用会很危险,滥用)

2)TestAndSet指令

简称 TS 指令,也有地方称为 TestAndSetLock 指令,或 TSL 指令
TSL 指令是用硬件实现的,执行的过程不允许被中断,只能一气呵成。以下是用C语言描述的逻辑:
在这里插入图片描述
若刚开始 lock 是 false,则 TSL 返回的 old 值为 false,while 循环条件不满足,直接跳过循环,进入临界区。若刚开始 lock 是 true,则执行 TLS 后 old 返回的值为 true,while 循环条件满足,会一直循环,直到当前访问临界区的进程在退出区进行“解锁”。
相比软件实现方法(双标志先检查法),TSL 指令把“上锁”和“检查”操作用硬件的方式变成了一气呵成的原子操作

优点:实现简单,无需像软件实现方法那样严格检查是否会有逻辑漏洞;适用于多处理机环境
缺点:不满足“让权等待”原则,暂时无法进入临界区的进程会占用CPU并循环执行TSL指令,从而导致“忙等”。

3)Swap指令

有的地方也叫 Exchange 指令,或简称 XCHG 指令
Swap 指令是用硬件实现的,执行的过程不允许被中断,只能一气呵成。以下是用C语言描述的逻辑:
在这里插入图片描述
逻辑上来看 Swap 和 TSL 并无太大区别,都是先记录下此时临界区是否已经被上锁(记录在 old 变量上),再将上锁标记 lock 设置为 true,最后检查 old,如果 old 为 false 则说明之前没有别的进程对临界区上锁,则可跳出循环,进入临界区。

优点:实现简单,无需像软件实现方法那样严格检查是否会有逻辑漏洞;适用于多处理机环境
缺点:不满足“让权等待”原则,暂时无法进入临界区的进程会占用CPU并循环执行TSL指令,从而导致“忙等”。

2.4.3 信号量

进程互斥的四种软件实现方式(单标志法、双标志先检查、双标志后检查、Peterson算法)
进程互斥的三种硬件实现方法(中断屏蔽方法、TS/TSL指令、Swap/XCHG指令)

  1. 在双标志先检查法中,进入区的“检查”、“上锁” 操作无法一气呵成,从而导致了两个进程有可能同时进入临界区的问题;
  2. 所有的解决方案都无法实现“让权等待”

1965年,荷兰学者Dijkstra提出了一种卓有成效的实现进程互斥、同步的方法——信号量机制

用户进程可以通过使用操作系统提供的一对原语来对信号量进行操作,从而很方便的实现了进程互斥、进程同步。

信号量其实就是一个变量 ,可以用一个信号量来表示系统中某种资源的数量,比如:系统中只有一台打印机,就可以设置一个初值为 1 的信号量。

原语是一种特殊的程序段,其执行只能一气呵成,不可被中断。原语是由关中断/开中断指令实现的。软件解决方案的主要问题是由“进入区的各种操作无法一气呵成”,因此如果能把进入区、退出区的操作都用“原语”实现,使这些操作能“一气呵成”就能避免问题。

一对原语wait(S) 原语和 signal(S) 原语,它们在执行时是不可中断的,可以把原语理解为我们自己写的函数,函数名分别为 wait和 signal,括号里的信号量 S 其实就是函数调用时传入的一个参数。
wait、signal 原语常简称为 P、V操作(来自荷兰语 proberen 和 verhogen)。因此,做题的时候常把wait(S)、signal(S) 两个操作分别写为 P(S)、V(S)

1. 整型信号量

用一个整数型的变量作为信号量,用来表示系统中某种资源的数量。(与普通整数变量的区别:对信号量的操作只有三种,即 初始化、P操作、V操作)
在这里插入图片描述
存在的问题:不满足“让权等待”原则,会发生“忙等”。

2. 记录型信号量

整型信号量的缺陷是存在“忙等”问题,因此人们又提出了“记录型信号量”,即用记录型数据结构表示的信号量。
在这里插入图片描述
在考研题目中 wait(S)、signal(S) 也可以记为 P(S)、V(S),这对原语可用于实现系统资源的“申请”和“释放”

S.value 的初值表示系统中某种资源的数目

对信号量 S 的一次P操作意味着进程请求一个单位的该类资源,因此需要执行 S.value–,表示资源数减1,当S.value < 0 时表示该类资源已分配完毕,因此进程应调用 block 原语进行自我阻塞(当前运行的进程从运行态→阻塞态),主动放弃处理机,并插入该类资源的等待队列 S.L 中。可见,该机制遵循了“让权等待”原则,不会出现“忙等”现象。

对信号量 S 的一次 V 操作意味着进程释放一个单位的该类资源,因此需要执行 S.value++,表示资源数加1,若加1后仍是 S.value <= 0,表示依然有进程在等待该类资源,因此应调用 wakeup 原语唤醒等待队列中的第一个进程(被唤醒进程从阻塞态→就绪态)。

在这里插入图片描述
注:若考试中出现 P(S)、V(S) 的操作,除非特别说明,否则默认 S 为记录型信号量。都采用记录型信号量进行解题。
一个信号量对应一种资源
信号量的值 = 这种资源的剩余数量(信号量的值如果小于0,说明此时有进程在等待这种资源)
P( S ) —— 申请一个资源S,如果资源不够就阻塞等待
V( S ) —— 释放一个资源S,如果有进程在等待该资源,则唤醒一个进程

3. 利用信号量实现进程互斥
  1. 分析并发进程的关键活动,划定临界区(如:对临界资源打印机的访问就应放在临界区)
  2. 设置互斥信号量 mutex,初值为 1(理解:信号量 mutex 表示“进入临界区的名额”)
  3. 在进入区 P(mutex)——申请资源
  4. 在退出区 V(mutex)——释放资源

在这里插入图片描述
注意:对不同的临界资源需要设置不同的互斥信号量P、V操作必须成对出现。缺少P(mutex) 就不能保证临界资源的互斥访问。缺少 V(mutex) 会导致资源永不被释放,等待进程永不被唤醒。
在这里插入图片描述

4. 利用信号量实现进程同步

进程同步:要让各并发进程按要求有序地推进。
在这里插入图片描述
在这里插入图片描述
用信号量实现进程同步:

  1. 分析什么地方需要实现“同步关系”,即必须保证“一前一后”执行的两个操作(或两句代码)
  2. 设置同步信号量 S, 初始为 0
  3. 在“前操作”之后执行 V(S)
  4. 在“后操作”之前执行 P(S) (技巧口诀:前V后P)
5. 利用信号量实现前驱关系

前驱关系是表示进程之间执行的先后顺序。P1→P2表示P2开始执行之前P1必须完成。

进程 P1 中有句代码 S1,P2 中有句代码 S2 ,P3中有句代码S3 …… P6 中有句代码 S6。这些代码要求按如下前驱图所示的顺序来执行:
在这里插入图片描述
其实每一对前驱关系都是一个进程同步问题(需要保证一前一后的操作)因此,

  1. 要为每一对前驱关系各设置一个同步信号量
  2. 在“前操作”之后对相应的同步信号量执行 V 操作
  3. 在“后操作”之前对相应的同步信号量执行 P 操作 (前V后P)

在这里插入图片描述

2.4.4 管程

在信号量机制中,每个要访问临界资源的进程都必须自备同步的PV操作,大量分散的同步操作给系统管理带来了麻烦,且容易因同步操作不当而导致死锁。如在生产者消费者问题中的同步操作不当就会导致死锁,如下图所示:
在这里插入图片描述
于是,便产生了一种新的进程同步工具——管程(管理临界资源的进程)。管程的特性保证了进程互斥,无须程序员自己实现互斥,从而降低了死锁发生的可能性。同时管程提供了条件变量,可以让程序员灵活地实现进程同步。

1. 管程的定义

代表共享资源的数据结构以及由对该共享数据结构实施操作的一组过程所组成的资源管理程序共同构成了一个操作系统的资源管理模块,我们称之为管程(monitor)。

  1. 管程是一种特殊的软件模块,有这些部分组成
    ① 局部于管程的共享数据结构说明;
    ② 对该数据结构进行操作的一组过程(也可以理解为“函数”);
    ③ 对局部于管程的共享数据设置初始值的语句;
    ④ 管程有一个名字

  2. 管程的基本特征
    ① 局部于管程的数据只能被局部于管程的过程所访问;
    ② 一个进程只有通过调用管程内的过程才能进入管程访问共享数据;
    每次仅允许一个进程在管程内执行某个内部过程

  3. 管程的定义描述举例如下:
    在这里插入图片描述
    ① 管程把对共享资源的操作封装起来(整体上很像一个类),管程内的共享数据结构只能被管程内的过程所访问。一个进程只有通过调用管程内的过程才能加入管程访问共享资源。对于上例,外部进程只能通过调用take_away()过程来申请一个资源;归还资源也一样。
    每次仅允许一个进程加入管程,从而实现进程互斥(注意:这种互斥特性是由编译器负责实现的,程序员不用关心)。若多个进程同时调用take_away(),give_back(),则只有某个进程运行完它调用的过程后,下个进程才能开始运行它调用的过程。也就是说,各个进程只能串行执行管理内的过程,这一特性保证了进程“互斥”访问共享数据结构S。

  4. 管程和进程的不同:
    ① 虽然两者都定义了数据结构,但进程定义的是私有数据结构PCB,管程定义的是公共数据结构,如消息队列等;
    ② 两者都存在对各自数据结构上的操作;但进程是由顺序程序执行有关操作,而管程主要是进行同步操作和初始化操作;
    ③ 设置进程的目的在于实现系统的并发性,而管程的设置则是解决共享资源的互斥使用问题;
    ④ 进程通过调用管程中的过程对共享数据结构实行操作,进程是主动工作方式,而管程为被动工作方式;
    ⑤ 进程之间能并发执行,而管程则不能与其调用者并发;
    ⑥ 进程具有动态性,由“创建”而诞生,由“撤销”而消亡,而管程则是操作系统中的一个资源管理模块,供进程调用。

2. 条件变量

当一个进程进入管程后被阻塞,直到阻塞原因解除是,在此期间,如果该进程不释放管程,你们其他管程无法进入管程,被迫长时间的等待。为此,将阻塞原因定义为条件变量 condition。通常,一个进程被阻塞的原因可以有多个,因此在管程中设置了多个条件变量。每个条件变量保存了一个等待队列,用于记录该条件变量对应被阻塞的所有进程,对条件变量只能进行两种操作,即wait和signal。

x.wait:当x对应的条件不满足时,正在调用管程的进程调用x.wait将自己插入x条件的等待队列,并释放管程。此时其他进程可以使用该管程。
x.signal:x对应的条件发生了变化,则调用x.signal,唤醒一个因x条件而阻塞的进程。

条件变量和信号量的比较:

  • 相似点:条件变量的wait/signal操作类似于信号量的P/V操作,可以实现进程的阻塞/唤醒。
  • 不同点:条件变量是“没有值”的,仅实现了“排队等待”功能;而信号量是“有值”的,信号量的值反映了剩余资源数,而在管程中,剩余资源数用共享数据结构记录。

2.4.5 经典同步问题

1. 生产者-消费者问题
  1. 问题描述
    系统中有一组生产者进程和一组消费者进程,生产者进程每次生产一个产品放入缓冲区,消费者进程每次从缓冲区中取出一个产品并使用。(注:这里的“产品”理解为某种数据)生产者、消费者共享一个初始为空、大小为n的缓冲区。只有缓冲区没满时,生产者才能把产品放入缓冲区,否则必须等待(满则阻塞等待)。只有缓冲区不空时,消费者才能从中取出产品,否则必须等待(空则阻塞等待)。缓冲区是临界资源,各进程必须互斥地访问
  2. 问题分析
    系统中有一组生产者进程和一组消费者进程,生产者进程每次生产一个产品放入缓冲区,消费者进程每次从缓冲区中取出一个产品并使用。(注:这里的“产品”理解为某种数据)
    生产者、消费者共享一个初始为空、大小为n的缓冲区
    只有缓冲区没满时,生产者才能把产品放入缓冲区,否则必须等待。(同步关系。缓冲区满时,生产者要等待消费者取走产品)
    只有缓冲区不空时,消费者才能从中取出产品,否则必须等待。(同步关系。缓冲区空时(即没有产品时),消费者要等待生产者放入产品)
    缓冲区是临界资源,各进程必须互斥地访问
    在这里插入图片描述
  3. PV操作题目分析步骤
    1. 关系分析。找出题目中描述的各个进程,分析它们之间的同步、互斥关系。
    2. 整理思路。根据各进程的操作流程确定P、V操作的大致顺序。
    3. 设置信号量。并根据题目条件确定信号量初值。(互斥信号量初值一般为1,同步信号量的初始值要看对应资源的初始值是多少

在这里插入图片描述
4. 能否改变相邻P、V操作的顺序?
在这里插入图片描述
若此时缓冲区内已经放满产品,则 empty=0,full=n。
则生产者进程执行① 使mutex变为0,再执行②,由于已没有空闲缓冲区,因此生产者被阻塞。
由于生产者阻塞,因此切换回消费者进程。消费者进程执行③,由于mutex为0,即生产者还没释放对临界资源的“锁”,因此消费者也被阻塞。
这就造成了生产者等待消费者释放空闲缓冲区,而消费者又等待生产者释放临界区的情况,生产者和消费者循环等待被对方唤醒,出现“死锁”。
同样的,若缓冲区中没有产品,即full=0,empty=n。按③④① 的顺序执行就会发生死锁。
因此,实现互斥的P操作一定要在实现同步的P操作之后。 V操作不会导致进程阻塞,因此两个V操作顺序可以交换
5. 利用管程解决生产者-消费者问题
在这里插入图片描述
引入管程的目的无非就是要更方便地实现进程互斥和同步

  1. 需要在管程中定义共享数据(如生产者消费者问题的缓冲区)。
  2. 需要在管程中定义用于访问这些共享数据的“入口”——其实就是一些函数(如生产者消费者问题中,可以定义一个函数用于将产品放入缓冲区,再定义一个函数用于从缓冲区取出产品)。
  3. 只有通过这些特定的“入口”才能访问共享数据
  4. 管程中有很多“入口”,但是每次只能开放其中一个“入口”,并且只能让一个进程或线程进入(如生产者消费者问题中,各进程需要互斥地访问共享缓冲区。管程的这种特性即可保证一个时间段内最多只会有一个进程在访问缓冲区。注意:这种互斥特性是由编译器负责实现的,程序员不用关心
  5. 可在管程中设置条件变量等待/唤醒操作以解决同步问题。可以让一个进程或线程在条件变量上等待(此时,该进程应先释放管程的使用权,也就是让出“入口”);可以通过唤醒操作将等待在条件变量上的进程或线程唤醒。

程序员可以用某种特殊的语法定义一个管程(比如: monitor ProducerConsumer …… end monitor;),之后其他程序员就可以使用这个管程提供的特定“入口”很方便地使用实现进程同步/互斥了。

2. 多生产者-多消费者
  1. 问题描述:
    桌子上有一只盘子,每次只能向其中放入一个水果。爸爸专向盘子中放苹果,妈妈专向盘子中放橘子,儿子专等着吃盘子中的橘子,女儿专等着吃盘子中的苹果。只有盘子空时,爸爸或妈妈才可向盘子中放一个水果。仅当盘子中有自己需要的水果时,儿子或女儿可以从盘子中取出水果。
    在这里插入图片描述

  2. 问题分析:

    1. 关系分析。找出题目中描述的各个进程,分析它们之间的同步、互斥关系。由于每次只能向其中放入一只水果可知,爸爸和妈妈是互斥关系。爸爸和女儿、妈妈和儿子是同步关系。儿子和女儿之间没有互斥和同步关系。
    2. 整理思路。根据各进程的操作流程确定P、V操作的大致顺序。(互斥:在临界区前后分别PV;同步:前V后P)
    3. 设置信号量。设置需要的信号量,并根据题目条件确定信号量初值。(互斥信号量初值一般为1,同步信号量的初始值要看对应资源的初始值是多少)
      在这里插入图片描述
  3. 实现过程:
    在这里插入图片描述
    图中还有一个问题:可不可以不用互斥信号量?
    刚开始,儿子、女儿进程即使上处理机运行也会被阻塞。如果刚开始是父亲进程先上处理机运行,则:父亲 P(plate),可以访问盘子→母亲 P(plate),阻塞等待盘子→父亲放入苹果 V(apple),女儿进程被唤醒,其他进程即使运行也都会阻塞,暂时不可能访问临界资源(盘子)→女儿 P(apple),访问盘子,V(plate),等待盘子的母亲进程被唤醒→母亲进程访问盘子(其他进程暂时都无法进入临界区)→……
    原因在于:本题中的缓冲区大小为1,在任何时刻,apple、orange、plate 三个同步信号量中最多只有一个是1。因此在任何时刻,最多只有一个进程的P操作不会被阻塞,并顺利地进入临界区…
    结论:即使不设置专门的互斥变量mutex,也不会出现多个进程同时访问盘子的现象。
    在这里插入图片描述
    扩展:如果盘子容量为2呢?
    如果盘子容量为2的话,即plate初始化为2。
    结论:父亲 P(plate),可以访问盘子→母亲 P(plate),可以访问盘子→父亲在往盘子里放苹果,同时母亲也可以往盘子里放橘子。于是就出现了两个进程同时访问缓冲区的情况,有可能导致两个进程写入缓冲区的数据相互覆盖的情况

    总结:在生产者-消费者问题中,如果缓冲区大小为1,那么有可能不需要设置互斥信号量就可以实现互斥访问缓冲区的功能。当然,这不是绝对的,要具体问题具体分析。
    建议:在考试中如果来不及仔细分析,可以加上互斥信号量,保证各进程一定会互斥地访问缓冲区。但需要注意的是,实现互斥的P操作一定要在实现同步的P操作之后,否则可能引起“死锁”。
    在这里插入图片描述

3. 吸烟者问题
  1. 问题描述:
    假设一个系统有三个抽烟者进程一个供应者进程。每个抽烟者不停地卷烟并抽掉它,但是要卷起并抽掉一支烟,抽烟者需要有三种材料:烟草、纸和胶水。三个抽烟者中,第一个拥有烟草、第二个拥有纸、第三个拥有胶水。供应者进程无限地提供三种材料,供应者每次将两种材料放桌子上,拥有剩下那种材料的抽烟者卷一根烟并抽掉它,并给供应者进程一个信号告诉完成了,供应者就会放另外两种材料再桌上,这个过程一直重复(让三个抽烟者轮流地抽烟
    在这里插入图片描述

  2. 问题分析:
    本质上这题也属于“生产者-消费者”问题,更详细的说应该是“可生产多种产品的单生产者-多消费者”。

    1. 关系分析。供应者与三个抽烟者分别是同步关系。由三个抽烟者轮流抽烟,所以三个抽烟者对抽烟这个动作互斥。
    2. 整理思路。显然这里有4个进程。供应者作为生产者向三个抽烟者提供材料。
    3. 设置信号量。信号量offer1,offer2,offer3分别表示烟草和纸组合的资源、烟草和胶水组合的资源、纸和胶水组合的资源。信号量finish用于互斥进行抽烟动作。

    在这里插入图片描述
    在这里插入图片描述

  3. 实现过程:
    在这里插入图片描述
    吸烟者问题可以为我们解决“可以生产多个产品的单生产者”问题提供一个思路。
    值得吸取的精华是:“轮流让各个吸烟者吸烟”必然需要“轮流的在桌上放上组合一、二、三”,注意体会我们是如何用一个整型变量 i 实现这个“轮流”过程的

4. 读者-写者问题
  1. 问题描述:
    有读者和写者两组并发进程,共享一个文件,当两个或两个以上的读进程同时访问共享数据时不会产生副作用,但若某个写进程和其他进程(读进程或写进程)同时访问共享数据时则可能导致数据不一致的错误。因此要求:①允许多个读者可以同时对文件执行读操作;②只允许一个写者往文件中写信息;③任一写者在完成写操作之前不允许其他读者或写者工作;④写者执行写操作前,应让已有的读者和写者全部退出。
    在这里插入图片描述

  2. 问题分析:

    1. 关系分析。由题目分析读者和写者是互斥的,写者和写者也是互斥的,而读者和读者不存在互斥问题。
    2. 整理思路。两个进程,即读者和写者。写者和任何进程互斥,用互斥信号量P、V即可解决。读者必须在实现与写者互斥的同时,实现与其他读者的同步,需要多用到一个计数器来判断当前是否有读者读文件。当有读者时,写者是无法写文件的,此时读者会一直占用文件;当没有读者时,写者才可以写文件。同时,这里不同读者对计数器的访问也应该是互斥的。
    3. 信号量设置。
  3. 实现过程:
    当信号量只有计数器和用于实现对共享文件的互斥访问rw时。思考:若两个读进程并发执行,则 count=0时两个进程也许都能满足 if 条件,都会执行P(rw),从而使第二个读进程阻塞的情况。
    在这里插入图片描述
    如何解决:出现上述问题的原因在于对count 变量的检查和赋值无法一气呵成,因此可以设置另一个互斥信号量来保证各读进程对count 的访问是互斥的。
    在这里插入图片描述
    在这里插入图片描述
    若希望写进程优先,即当有读进程正在读共享文件时,即当有读进程正在读共享文件时,有写进程请求访问,这时应禁止后续读进程的请求,等到已在共享文件的读进程执行完毕,立即让写进程执行,只有在无写进程执行的情况下才允许读进程再次运行。因此,增加了一个信号量w。
    在这里插入图片描述

  4. 总结:
    读者-写者问题为我们解决复杂的互斥问题提供了一个参考思路。
    核心思想在于设置了一个计数器 count 用来记录当前正在访问共享文件的读进程数。我们可以用count 的值来判断当前进入的进程是否是第一个/最后一个读进程,从而做出不同的处理。另外,对 count 变量的检查和赋值不能一气呵成导致了一些错误,如果需要实现“一气呵成”,自 然应该想到用互斥信号量

5. 哲学家进餐问题
  1. 问题描述:
    一张圆桌上坐着5名哲学家,每两个哲学家之间的桌上摆一根筷子,桌子的中间是一碗米饭。哲学家们倾注毕生的精力用于思考和进餐,哲学家在思考时,并不影响他人。只有当哲学家饥饿时,才试图拿起左、右两根筷子(一根一根地拿起)。如果筷子已在他人手上,则需等待。饥饿的哲学家只有同时拿起两根筷子才可以开始进餐,当进餐完毕后,放下筷子继续思考。
    在这里插入图片描述

  2. 问题分析:

    1. 关系分析。系统中有5个哲学家进程,5位哲学家与左右邻居对其中间筷子的访问是互斥关系。
    2. 整理思路。这个问题中只有互斥关系,但与之前遇到的问题不同的事,每个哲学家进程需要同时持有两个临界资源才能开始吃饭。如何避免临界资源分配不当造成的死锁现象,是哲学家问题的精髓。
    3. 信号量设置。定义互斥信号量数组chopstick[5]={1,1,1,1,1} 用于实现对5个筷子的互斥访问。并对哲学家按0~4编号,哲学家 i 左边的筷子编号为 i,右边的筷子编号为 (i+1)%5。
  3. 实现过程:
    当5名哲学家都想要进餐并分别拿起左边的筷子时(都恰好执行完P(chopstick[i]))筷子已被拿光,等到他们想再拿右边的筷子时就会全被阻塞,因此出现了死锁。
    在这里插入图片描述
    所以这时出现了一个问题,如何防止死锁的发生呢?
    ① 可以对哲学家进程施加一些限制条件,比如最多允许四个哲学家同时进餐。这样可以保证至少有一个哲学家是可以拿到左右两只筷子的。(破坏互斥条件)

    第①中的代码如下:
    在这里插入图片描述

    ② 要求奇数号哲学家先拿左边的筷子,然后再拿右边的筷子,而偶数号哲学家刚好相反。用这种方法可以保证如果相邻的两个奇偶号哲学家都想吃饭,那么只会有其中一个可以拿起第一只筷子,另一个会直接阻塞。这就避免了占有一支后再等待另一只的情况。

    ③ 仅当一个哲学家左右两支筷子都可用时才允许他抓起筷子。(破坏请求和保持条件)

    第③种情况的代码实现如下:
    在这里插入图片描述

  4. 总结:
    哲学家进餐问题的关键在于解决进程死锁
    这些进程之间只存在互斥关系,但是与之前接触到的互斥关系不同的是,每个进程都需要同时持有两个临界资源,因此就有“死锁”问题的隐患。
    如果在考试中遇到了一个进程需要同时持有多个临界资源的情况,应该参考哲学家问题的思想,分析题中给出的进程之间是否会发生循环等待,是否会发生死锁。可以参考哲学家就餐问题解决死锁的三种思路。

2.4.6 补充

  1. 在多道程序技术中,信号量机制是一种有效实现进程同步和互斥的工具。进程执行的前趋关系实质上是指进程的同步关系。
  2. 临界资源是互斥共享资源,非共享数据不属于临界资源。临界资源与共享资源的区别在于,在一段时间内能否允许被多个进程访问(并发使用)。公共队列可供多个进程使用,但一次只可供一个进程使用,若多个进程同时使用公共队列的话,势必造成队列中的数据混乱而无法使用,所以公共队列属于临界资源。而磁盘存储介质和可重入的程序代码(一次可供多个进程使用)都属于共享资源。
  3. 在操作系统中,要对并发进程进行同步的原因是并发进程是异步的
  4. 在操作系统中,P、V操作是一种低级的进程通信原语,它是不能被中断的。不属于系统调用。
  5. 原语是不可分割的指令序列。是原子性的、不可分割的操作。其严格定义为:由若干机器指令构成的完成某种特定功能的一段程序,其执行必须是连续的,在执行过程中不允许被中断。
  6. 在信号量机制实现互斥时,互斥信号量的初值为1;而实现同步时,同步信号量的初值由用户确定,是根据具体情况来决定的。
  7. 可以被多个进程在任意时刻共享的代码必须是不允许任何修改的代码。这样的代码就是可重入代码,也称纯代码,即允许多个进程同时访问的代码。所以共享程序段必须可重入编码,否则无法实现共享的功能。
  8. 一个进程因在互斥信号量mutex上执行V(mutex)操作而导致唤醒另一个进程时,执行V操作后mutex的值小于等于0。因为系统原来存在等待进入临界区的进程,mutex小于等于-1。
  9. 管程的signal操作与信号量机制中的V操作不同,信号量机制中的V操作一定会改变信号量的值S=S+1.而管程中的signal操作时针对某个条件变量的,若不存在因该条件而阻塞的进程,则signal不会产生任何影响。
  10. 并发进程之间的共享没有必然的要求,只有执行时间上的偶然重合,可能无关也可能有交往
  11. 若x是管程内的条件变量,则当进程执行x.wait()时所做的动作是阻塞该进程,并将之插入x的阻塞队列中。在同一时刻,管程中只能有一个进程在执行。若进程A执行了x.wait()操作,则该进程会阻塞,并挂到条件变量x对应的阻塞队列上。这样,管程的使用权被释放,就可以有另一个进程进入管程,所以这时要是有进程B执行x.signal()操作,则会唤醒x队列的阻塞队列的队首进程。wait和signal操作有点类似于信号量的P、V操作,不过P、V操作会对信号量进行修改操作、简称操作以及在信号量在满足一定条件的情况下将进程阻塞或唤醒;而管程下的wait和signal操作只实现了对进程的阻塞或唤醒。
  12. 硬件方法实现进程同步时不能实现让权等待;Peterson算法满足有限等待但不满足让权等待;记录型信号量由于引入阻塞机制,消除了不让权等待的情况。
  13. 实现临界区互斥机制不一定非要实现“让权等待”准则,如Peterson算法。

2.5 死锁

2.5.1 死锁的概念

1. 资源问题

在系统许多不同类型的资源中,其中可以引起死锁的主要是,需要采用互斥访问方法的、不可以背抢占的资源,即临界资源。

1)可重用性资源

可重用性资源是一种可供用户重复使用多次的资源,它具有如下性质:

  1. 每一个可重用性资源中的单元只能分配给一个进程使用,不允许多个进程共享。
  2. 进程在使用可重用性资源是,须按照这样的顺序:
    ① 请求资源。如果请求资源失败,请求进程将会被阻塞或循环等待。
    ② 使用资源。进程对资源进行操作,如用打印机进行打印。
    ③ 释放资源。当进程使用完自己释放资源。
  3. 系统中每一类可重用性资源中的单元数目是相对固定的,进程在运行期间既不能创建也不能删除它。

对资源的请求和释放通常都是利用系统调用来实现的。

2)可消耗性资源

可消耗性资源又称为临时性资源,它是在进程运行期间,由进程动态地创建和消耗的。通常是由生产者进程创建,由消费者进程消耗。最典型的可消耗性资源就是用于进程间通信的消息等。

3)可抢占性资源

是指某进程在获得这类资源后,该资源可以再被其他进程或系统抢占。例如优先级高的进程可以抢占优先级低的进程的处理机。又如可以把一个进程从一个存储区转移到另一个存储区,在内存紧张时,还可将一个进程从内存调出到外存上,即抢占该进程在内存的空间。可见,CPU和主存均属于可抢占性资源。对于这类资源是不会引起死锁的。

4)不可抢占性资源

另一类资源是不可抢占性资源,即一旦系统把某资源分配给该进程后,就不能将它强行收回,只能在进程用完后自行释放。磁带机、打印机等就属于不可抢占性资源。

2. 死锁的定义

死锁的起因,通常是源于多个进程对资源的争夺,不仅对不可抢占资源进行争夺时会引起死锁,而且对可消耗资源进行争夺时,也会引起死锁。

在并发环境下,各进程因竞争资源而造成的一种互相等待对方手里的资源,导致各进程都阻塞,都无法向前推进的现象,就是“死锁”。发生死锁后若无外力干涉,这些进程都将无法向前推进。即每个进程都占有一个资源,同时又在等待另一个进程手里的资源。就会发生“死锁”。

例如,在哲学家进餐问题中,如果每一个哲学家因饥饿都拿起了他们左边的筷子,当每一个哲学家又试图去拿起他们右边的筷子时,将会因无筷子可拿而无限期地等待,所以就出现了“死锁”的问题。
在这里插入图片描述

3. 死锁、饥饿、死循环的区别

死锁:各进程互相等待对方手里的资源,导致各进程都阻塞,无法向前推进的现象。
饥饿:由于长期得不到想要的资源,某进程无法向前推进的现象。比如:在短进程优先(SPF)算法中,若有源源不断的短进程到来,则长进程将一直得不到处理机,从而发生长进程“饥饿”。
死循环:某进程执行过程中一直跳不出某个循环的现象。有时是因为程序逻辑 bug 导致的,有时是程序员故意设计的。
在这里插入图片描述

4. 死锁产生的原因
1)系统资源的竞争

各进程对不可剥夺的资源(如打印机)的竞争可能引起死锁,对可剥夺的资源(CPU)的竞争是不会引起死锁的。

2)进程推进顺序非法

进程推进顺序非法。请求和释放资源的顺序不当,也同样会导致死锁。例如,并发执行的进程P1、P2 分别申请并占有了资源 R1、R2,之后进程P1又紧接着申请资源R2,而进程P2又申请资源R1,两者会因为申请的资源被对方占有而阻塞,从而发生死锁。

3)信号量使用不当

信号量的使用不当也会造成死锁。如生产者-消费者问题中,如果实现互斥的P操作在实现同步的 P操作之前,就有可能导致死锁。(可以把互斥信号量、同步信号量也看做是一种抽象的系统资
源)

总之,对不可剥夺资源的不合理分配,可能导致死锁。产生死锁的原因包括资源竞争使用(某类资源数量不够)、进程间推进顺序不当。

4)死锁产生的必要条件

产生死锁必须同时满足一下四个条件,只要其中任一条件不成立,死锁就不会发生

  1. 互斥条件:只有对必须互斥使用的资源的争抢才会导致死锁(如哲学家的筷子、打印机设备)。像内存、扬声器这样可以同时让多个进程使用的资源是不会导致死锁的(因为进程不用阻塞等待这种资源)。
  2. 不剥夺条件:进程所获得的资源在未使用完之前,不能由其他进程强行夺走,只能主动释放。
  3. 请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源又被其他进程占有,此时请求进程被阻塞,但又对自己已有的资源保持不放。
  4. 循环等待条件:存在一种进程资源的循环等待链,链中的每一个进程已获得的资源同时被下一个进程所请求。(注意!发生死锁时一定有循环等待,但是发生循环等待时未必死锁(循环等待是死锁的必要不充分条件))
    在这里插入图片描述

如果同类资源数大于1,则即使有循环等待,也未必发生死锁。但如果系统中每类资源都只有一个,那循环等待就是死锁的充分必要条件了。
在这里插入图片描述

2.5.2 死锁的处理策略

为使系统不发生死锁,必须设法破坏产生死锁的四个必要条件之一,或允许死锁产生,但当死锁发生时能检测出死锁,并有能力实现恢复。

  1. 死锁预防
    设置某些限制条件,破坏产生死锁的4个必要条件中的一个或几个,以防止产生死锁。

  2. 避免死锁
    用某种方法防止系统进入不安全状态,从而避免死锁(银行家算法)。

  3. 死锁的检测及解除
    允许死锁的发生,不过操作系统会负责检测出死锁的发生,然后采取某种措施解除死锁。

预防死锁和避免死锁都属于事先预防都属于事先预防策略,预防死锁的限制条件比较严格,实现起来较为简单,但往往导致系统的效率低,资源利用率低;避免死锁的限制条件相对宽松,资源分配后需要通过算法来判断是否进入不安全状态,实现起来较为复杂。
在这里插入图片描述

2.5.3 死锁预防

防止死锁的发生只需破坏死锁产生的4个必要条件之一即可。

1. 破坏互斥条件

如果把只能互斥使用的资源改造为允许共享使用,则系统不会进入死锁状态。比如: SPOOLing技术。操作系统可以采用 SPOOLing 技术把独占设备在逻辑上改造成共享设备。比如,用SPOOLing技术将打印机改造为共享设备…
在这里插入图片描述
该策略的缺点:并不是所有的资源都可以改造成可共享使用的资源。并且为了系统安全,很多地方还必须保护这种互斥性。因此,很多时候都无法破坏互斥条件

2. 破坏不剥夺条件

方案一:当某个进程请求新的资源得不到满足时,它必须立即释放保持的所有资源,待以后需要时再重新申请。也就是说,即使某些资源尚未使用完,也需要主动释放,从而破坏了不可剥夺条件。

方案二:当某个进程需要的资源被其他进程所占有的时候,可以由操作系统协助,将想要的资源强行剥夺。这种方式一般需要考虑各进程的优先级(比如:剥夺调度方式,就是将处理机资源强行剥夺给优先级更高的进程使用)

该策略的缺点

  1. 实现起来比较复杂。
  2. 释放已获得的资源可能造成前一阶段工作的失效。因此这种方法一般只适用于易保存和恢复状态的资源,如CPU。
  3. 反复地申请和释放资源会增加系统开销,降低系统吞吐量。
  4. 若采用方案一,意味着只要暂时得不到某个资源,之前获得的那些资源就都需要放弃,以后再重新申请。如果一直发生这样的情况,就会导致进程饥饿。
3. 破坏请求和保持条件

可以采用静态分配方法,即进程在运行前一次申请完它所需要的全部资源,在它的资源未满足前,不让它投入运行。一旦投入运行后,这些资源就一直归它所有,该进程就不会再请求别的任何资源了。

该策略实现起来简单,但也有明显的缺点
有些资源可能只需要用很短的时间,因此如果进程的整个运行期间都一直保持着所有资源,就会造成严重的资源浪费,资源利用率极低。另外,该策略也有可能导致某些进程饥饿
在这里插入图片描述

4. 破坏循环等待条件

可采用顺序资源分配法。首先给系统中的资源编号,规定每个进程必须按编号递增的顺序请求资源,同类资源(即编号相同的资源)一次申请完。

原理分析:一个进程只有已占有小编号的资源时,才有资格申请更大编号的资源。按此规则,已持有大编号资源的进程不可能逆向地回来申请小编号的资源(除非先释放所有具有相同和更高序号的资源后,才能申请序号低的资源),从而就不会产生循环等待的现象。
在这里插入图片描述

该策略的缺点

  1. 不方便增加新的设备,因为可能需要重新分配所有的编号;
  2. 进程实际使用资源的顺序可能和编号递增顺序不一致,会导致资源浪费;
  3. 必须按规定次序申请资源,用户编程麻烦。

在这里插入图片描述

2.5.4 死锁避免

避免死锁同样属于事先预防策略,但并不是事先采取某种限制措施破坏死锁的必要条件,而是在资源动态分配过程中,防止系统进入不安全状态,以避免发生死锁。这种方式所施加的限制条件较弱,可以获得较好的系统性能。

1. 系统安全状态

假如你是一位成功的银行家,手里掌握着100个亿的资金…
有三个企业想找你贷款,分别是 企业B、企业A、企业T,为描述方便,简称BAT。
B 表示:“大哥,我最多会跟你借70亿…”
A 表示:“大哥,我最多会跟你借40亿…”
T 表示:“大哥,我最多会跟你借50亿…”
然而…江湖中有个不成文的规矩:如果你借给企业的钱总数达不到企业提出的最大要求,那么不管你之前给企业借了多少钱,那些钱都拿不回来了…
刚开始,BAT三个企业分别从你这儿借了 20、10、30 亿 …
在这里插入图片描述
在这里插入图片描述
所以A借20亿后,还是安全的,然后所形成的顺序被我们称为安全队列
但若是B想借30亿的话则是不安全的。
在这里插入图片描述

所谓安全序列,就是指如果系统按照这种序列分配资源,则每个进程都能顺利完成。只要能找出一个安全序列,系统就是安全状态。当然,安全序列可能有多个

如果分配了资源之后,系统中找不出任何一个安全序列,系统就进入了不安全状态。这就意味着之后可能所有进程都无法顺利的执行下去(死锁)。当然,如果有进程提前归还了一些资源(比如A 先归还了10亿,那么就有安全序列T→B→A),那系统也有可能重新回到安全状态,不过我们在分配资源之前总是要考虑到最坏的情况。

如果系统处于安全状态,就一定不会发生死锁。如果系统进入不安全状态,就可能发生死锁(处于不安全状态未必就是发生了死锁,但发生死锁时一定是在不安全状态)

因此可以在资源分配之前预先判断这次分配是否会导致系统进入不安全状态,以此决定是否答应资源分配请求。这也是“银行家算法”的核心思想。

2. 银行家算法

银行家算法是荷兰学者 Dijkstra 为银行系统设计的,以确保银行在发放现金贷款时,不会发生不能满足所有客户需要的情况。后来该算法被用在操作系统中,用于避免死锁

核心思想:在进程提出资源申请时,先预判此次分配是否会导致系统进入不安全状态。如果会进入不安全状态,就暂时不答应这次请求,让该进程先阻塞等待

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可满足P1需求,将 P1 加入安全序列,并更新剩余可用资源值为 (5, 3, 2)
依次检查剩余可用资源 (5, 3, 2) 是否能满足剩余进程(不包括已加入安全序列的进程)的需求
在这里插入图片描述
可满足P3需求,将 P3 加入安全序列,并更新剩余可用资源值为 (7, 4, 3)
此时安全序列为:P1 → P3
在这里插入图片描述
依次检查剩余可用资源 (7, 4, 3) 是否能满足剩余进程(不包括已加入安全序列的进程)的需求……
……
以此类推,共五次循环检查即可将5个进程都加入安全序列中,最终可得一个安全序列(P1 → P3 → P0 → P2 → P4)。该算法称为安全性算法。可以很方便地用代码实现以上流程,每一轮检查都从编号较小的进程开始检查。实际做题时可以更快速的得到安全序列。

实际做题(手算)时可用更快速的方法找到一个安全序列
经对比发现,(3, 3, 2)可满足 P1、P3,说明无论如何,这两个进程的资源需求一定是可以依次被满足的,因此P1、P3 一定可以顺利的执行完,并归还资源。 可把 P1、P3 先加入安全序列。
(2, 0, 0) + (2, 1, 1) + (3, 3, 2) = (7, 4, 3)
剩下的 P0、P2、P4 都可被满足。同理,这些进程都可以加入安全序列。
于是,5个进程全部加入安全序列,说明此时系统处于安全状态暂不可能发生死锁

在这里插入图片描述

1)银行家算法的数据结构

在这里插入图片描述
假设系统中有 n 个进程,m 种资源
每个进程在运行前先声明对各种资源的最大需求数,则可用一个 n * m 的矩阵(可用二维数组实现)表示所有进程对各种资源的最大需求数。不妨称为最大需求矩阵 Max,Max[i, j]=K 表示进程 Pi 最多需要 K 个资源 Rj。同理,系统可以用一个 n*m 的分配矩阵Allocation表示对所有进程的资源分配情况。Max – Allocation = Need 矩阵,表示各进程最多还需要多少各类资源。
另外,还要用一个长度为m的一维数组 Available 表示当前系统中还有多少可用资源。某进程 Pi 向系统申请资源,可用一个长度为m的 一维数组 Requesti 表示本次申请的各种资源量。

2)银行家算法的描述

设Requesti 是进程Pi 的请求向量,如果Requesti[j]=K,表示进程Pi需要K个Rj类型的资源。当Pi发出资源请求后,系统按下述步骤进行检查:

  1. 如果 Requesti[j]≤Need[i, j] (0≤j≤m)便转向第二步;否则认为出错。 (因为它所需要的资源数已超过它所宣布的最大值。)
  2. 如果 Requesti[j]≤Available[j] (0≤j≤m),便转向第三步 ;否则表示尚无足够资源,Pi必须等待。
  3. 系统试探着把资源分配给进程Pi,并修改相应的数据(并非真的分配,修改数值只是为了做预判):
    Available = Available - Requesti;
    Allocation[i, j] = Allocation[i, j] + Requesti[j];
    Need[i, j] = Need[i, j] – Requesti[j]
  4. 操作系统执行安全性算法,检查此次资源分配后,系统是否处于安全状态。若安全,才正式分配否则,恢复相应数据,让进程阻塞等待
3)安全性算法

简言之就是,检查当前的剩余可用资源是否能满足某个进程的最大需求,如果可以,就把该进程加入安全序列,并把该进程持有的资源全部回收。
不断重复上述过程,看最终是否能让所有进程都加入安全序列。其实,具体步骤上面例子已经有展示了。

最后,提醒一下,系统处于不安全状态未必死锁,但死锁时一定处于不安全状态。系统处于安全状态一定不会死锁。

2.5.5 死锁检测和解除

在这里插入图片描述
如果系统中既不采取预防死锁的措施,也不采取避免死锁的措施,系统就很可能发生死锁。在这种情况下,系统应当提供两个算法:
死锁检测算法:用于检测系统状态,以确定系统中是否发生了死锁。
死锁解除算法:当认定系统中已经发生了死锁,利用该算法可将系统从死锁状态中解脱出来。

1. 死锁检测

为了能对系统是否已发生了死锁进行检测,必须:
①用某种数据结构来保存资源的请求和分配信息;
②提供一种算法,利用上述信息来检测系统是否已进入死锁状态。
在这里插入图片描述
如果系统中剩余的可用资源数足够满足进程的需求,那么这个进程暂时是不会阻塞的,可以顺利地执行下去。
在这里插入图片描述

如果这个进程执行结束了把资源归还系统,就可能使某些正在等待资源的进程被激活,并顺利地执行下去。相应的,这些被激活的进程执行完了之后又会归还一些资源,这样可能又会激活另外一些阻塞的进程…
在这里插入图片描述
如果按上述过程分析,最终能消除所有边,就称这个图是可完全简的。此时一定没有发生死锁(相当于能找到一个安全序列)。

如果最终不能消除所有边,那么此时就是发生了死锁
在这里插入图片描述
最终还连着边的那些进程(P1和P2)就是处于死锁状态的进程。

检测死锁的算法:
1)在资源分配图中,找出既不阻塞又不是孤点的进程 Pi(即找出一条有向边与它相连,且该有向边对应资源的申请数量小于等于系统中已有空闲资源数量。如下图中,R1没有空闲资源,R2有一个空闲资源。若所有的连接该进程的边均满足上述条件,则这个进程能继续运行直至完成,然后释放它所占有的所有资源)。消去它所有的请求边和分配变,使之称为孤立的结点。在下图中,P1 是满足这一条件的进程结点,于是将P1的所有边消去。
在这里插入图片描述

2)进程 Pi 所释放的资源,可以唤醒某些因等待这些资源而阻塞的进程,原来的阻塞进程可能变为非阻塞进程。在下图中,P2 就满足这样的条件。根据1)中的方法进行一系列简化后,若能消去途中所有的边,则称该图是可完全简化的

检测死锁其实就是依次消除与不阻塞相连的边,直到无边可销。

死锁定理:如果某时刻系统的资源分配图是不可完全简化的,那么此时系统死锁

2. 死锁解除

一旦检测出死锁的发生,就应该立即解除死锁。
补充:并不是系统中所有的进程都是死锁状态,用死锁检测算法化简资源分配图后,还连着边的那些进程就是死锁进程。

解除死锁的主要方法有:

  1. 资源剥夺法。挂起(暂时放到外存上)某些死锁进程,并抢占它的资源,将这些资源分配给其他的死锁进程。但是应防止被挂起的进程长时间得不到资源而饥饿。
  2. 撤销进程法(或称终止进程法)。强制撤销部分、甚至全部死锁进程,并剥夺这些进程的资源。这种方式的优点是实现简单,但所付出的代价可能会很大。因为有些进程可能已经运行了很长时间,已经接近结束了,一旦被终止可谓功亏一篑,以后还得从头再来。
  3. 进程回退法。让一个或多个死锁进程回退到足以避免死锁的地步。这就要求系统要记录进程的历史信息,设置还原点。

终止进程时应考虑的因素:

  1. 进程的优先级的大小;
  2. 进程已执行了多少时间,还需要多少时间才能完成
  3. 进程在运行中已经使用多少资源,以后还需多少资源;
  4. 进程的性质是交互式的还是批处理式的。

2.5.6 补充

  1. 死锁一定要有两个或两个以上的进程才会导致,而饥饿可能由一个进程导致。
  2. 一次分配所有资源的方法是破坏死锁的四个必要条件中的请求和保持条件
  3. 系统死锁的可能原因主要是时间上空间上的。时间上由于进程运行中推进顺序不当,即调用时间不合适,不该切换进程时进行了切换,可能会造成死锁。空间上的原因是对独占资源分配不当,互斥资源部分分配又不可剥夺,极易造成死锁。
    系统资源不足不是造成死锁的原因
  4. 引入多道程序技术的前提条件之一是系统具有中断功能。多道程序技术要求进程间实现并发,而并发性的实现需要中断功能的支持。
  5. 死锁的四个必要条件中,无法破坏的是互斥使用资源。所谓破坏互斥使用资源,是指允许多个进程同时访问资源,但有些资源根本不能同时访问,如打印机只能互斥使用。因此破坏互斥条件而预防死锁的方法不太可行。而且有的场合还应保护这种互斥性。
  6. 系统的资源分配图中,如出现了环路是无法判断是否处于死锁状态的。出现了,只是满足了循环等待的必要条件,而满足必要条件不一定会导致死锁。要是每种资源只有一个且出现了环路,就一定会发生死锁。
  7. 产生死锁的根本原因是系统资源分配不足进程推进顺序非法
  8. 死锁定理是用于检测死锁的方法。
  9. 死锁在系统中不可能完全消灭,但我们要尽可能地减少死锁的发生。对死锁的处理有4种方法:忽略、检测和解除、避免与预防,每种方法对死锁的处理从宽到严,同时系统并发性由大到小

其他章节:
操作系统王道考研复习——第一章(计算机系统概述)
操作系统王道考研复习——第二章(进程管理) 上
操作系统王道考研复习——第三章(内存管理/存储器管理)
操作系统王道考研复习——第四章(文件管理)
操作系统王道考研复习——第五章(设备管理)

  • 34
    点赞
  • 73
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值