2021-10-31 《计算机操作系统》(第四版)学习笔记:第三章

第三章 处理机调度与死锁

3.1 处理机调度的层次和调度算法的目标
3.1.1 处理机调度的层次

(1)高级调度

  • 又称长程调度作业调度,调度对象是作业
  • 主要功能是根据某种算法将外存上处于后备队列的哪几个作业调入内存
  • 主要用于多道批处理系统。不存在于分时和实时系统中

(2)低级调度

  • 又称短程调度进程调度,调度对象是进程
  • 主要功能是根据某种算法决定就绪队列中的哪个进程获得 CPU
  • 在多道批处理、分时和实时三个系统中都有配置

(3)中级调度

  • 又称内存调度中程调度
  • 主要功能是把暂时不能运行的进程调至外存等待(挂起),把外存上具备运行条件的就绪进程重新调入内存(唤醒)
  • 主要目的是提高内存利用率和系统吞吐量

​ 在分时系统中,短程(进程)调度的周期约为 10 ~ 100 ms;远程(作业)调度的周期约为几分钟;中程调度的周期在二者之间。

3.1.2 处理机调度算法的目标

(1)处理机调度算法的共同目标

  1. 资源利用率
    CPU 的利用率 = CPU 有效工作时间 CPU 有效工作时间 + CPU 空闲等待时间 \text{CPU 的利用率}= \displaystyle{\text{CPU 有效工作时间}\over \text{CPU 有效工作时间 + CPU 空闲等待时间}} CPU 的利用率=CPU 有效工作时间 + CPU 空闲等待时间CPU 有效工作时间

  2. 公平性

    各个进程都获得合理的 CPU 时间,不发生进程饥饿现象。

  3. 平衡性

    系统中的 CPU 和各种外部设备经常处于忙碌状态,平衡使用系统资源。

  4. 策略强制执行

    处于某些原因(安全策略等),即使会造成某些工作的延迟也要执行。

(2)批处理系统的目标

  1. 平均周转时间短

    周转时间: 作业被提交给系统开始,到作业完成为止的这段时间间隔。

    可以使用平均值来表示平均周转时间:
    T = 1 n ∑ i = 1 n T i T={1\over n}\sum_{i=1}^{n}T_{i} T=n1i=1nTi
    为了进一步反映调度的性能,使用带权周转时间替换周转时间。

    带权周转时间: 作业周转时间 T 与系统为他提供服务的时间 Ts 之比。平均带权周转时间越少越好:
    W = 1 n ∑ i = 1 n T i T s W={1\over n}\sum_{i=1}^{n}{T_{i}\over T_{s}} W=n1i=1nTsTi

  2. 系统吞吐量高

    吞吐量: 单位时间内系统所完成的作业数。

    若单纯地提高系统吞吐量,就应该尽可能多地选择短作业运行。

  3. 处理机利用率高

    若单纯地提高处理机利用率,就应该尽可能多地选择计算量大的作业运行。

​ 由此可见,有些要求之间是存在一定矛盾的。

(3)分时系统的目标

  1. 响应时间快

  2. 均衡性

    系统响应时间的快慢应与用户所请求服务的复杂性相适应

(4)实时系统的目标

  1. 截止时间的保证
  2. 可预测性
3.2 作业与作业调度
3.2.1 批处理系统中的作业

(1)作业和作业步

  1. 作业

    作业是一个比程序更为广泛的概念。它不仅包含通常的程序和数据,还应配有一份作业说明书。在批处理系统中,以作业为单位从外存调入内存。

  2. 作业步

    在作业运行期间,每个作业都必须经过若干个相对独立又相互关联的顺序加工步骤才能得到结果,我们把每一个加工步骤称为一个作业步。

    一个典型的作业可以分成:

    • “编译”作业步
    • “链接装配”作业步
    • “运行”作业步

(2)作业控制块(JCB)

​ 在多道批处理系统中,为每个作业设置了一个作业控制块 JCB,作为作业在系统中存在的标志。

​ 每当一个作业进入系统时,便由“作业注册”程序为该作业建立一个作业控制块 JCB。

(3)作业运行的三个阶段和三种状态

​ 三个阶段:

  • 收容

    操作员把用户提交的作业输入到硬盘上,为其建立 JCB,放入后备队列中。

  • 运行

  • 完成

​ 三种状态:

  • 后备状态
  • 运行状态
  • 完成状态
3.2.2 作业调度的主要任务
  1. 接纳多少个作业
  2. 接纳哪些作业
3.2.3 先来先服务调度算法(FCFS)和短作业优先(SJF)调度算法

(1)先来先服务调度算法(First-Come First-Served)

​ 该算法既可以用于作业调度,也可以用于进程调度。

​ FCFS 算法在单处理机系统中很少作为主调度算法,经常与其他调度算法结合使用。

(2)短作业优先调度算法(Short Job First)

​ 作业越短,优先级越高。作业长短以作业所要求的运行时间来衡量。

​ 缺点:

  1. 必须预知作业的运行时间
  2. 对长作业非常不利
  3. 人机无法实现交互
  4. 未考虑作业的紧迫程度
3.2.4 优先级调度算法(PSA)和高响应比优先调度算法(HRRN)

(1)优先级调度算法(Priority-Scheduling Algorithm)

​ 基于作业的紧迫程度,由外部赋予作业相应的优先级,系统从后备队列中选择若干个优先级最高的作业装入内存。

(2)高响应比优先调度算法(Highest Response Ratio Next)

​ FCFS:仅考虑了作业的等待时间

​ SJF:仅考虑了作业的运行时间

​ 该算法结合二者,引入了一个动态优先级(可改变):
优先权 R P = 等待时间 + 要求服务时间 要求服务时间 = 响应时间 要求服务时间 \text{优先权}R_{P}={\text{等待时间 + 要求服务时间}\over\text{要求服务时间}} ={\text{响应时间}\over\text{要求服务时间}} 优先权RP=要求服务时间等待时间 + 要求服务时间=要求服务时间响应时间
​ 这样长作业的优先级在等待期间不断增加,最终必然有机会获得处理机。

  • 作业等待时间相同时,要求服务时间越短,优先权越高。类似 SJF
  • 要求服务时间相同时,等待时间越短,优先权越高。类似 FCFS
  • 对于长作业而言,不会出现“死等”现象
  • 每次进行调度时,都要做响应比的计算,系统开销增加
3.3 进程调度
3.3.1 进程调度的任务、机制和方式

(1)进程调度的任务

  • 保存处理机的现场信息
  • 按某种算法选取进程
  • 把处理机分配给进程

(2)进程调度的机制

​ 为了实现进程调度,应具备以下三个基本部分:

  1. 排队器

    事先将系统中的所有就绪进程按照一定的策略排成一个或多个队列,以便调度程序能够最快地找到它。

  2. 分派器

    依据进程调度程序所选定的进程,将其从就绪队列中取出,进行上下文切换,分配处理机。

  3. 上下文切换器

    • OS 保存当前进程的上下文到其进程控制块内的相应单元,再装入分派程序的上下文
    • 移出分派程序的上下文,将新进程 CPU 现场信息装入到处理机的寄存器中,开始运行新进程

(3)进程调度的方式

  1. 非抢占方式(Nonpreemptive Mode)

    一旦把处理机分配给某进程后,就一直让它运行下去,不因为时钟中断或任何其他原因去抢占处理机,直到进程完成或发生阻塞。

    引起进程调度的因素有三:

    • 进程执行完毕或无法继续运行
    • 执行了某种原语操作(Block)
    • 提出 I / O 请求
  2. 抢占方式(Preemptive Mode)

    允许调度程序根据某种原则暂停某个正在执行的进程,并将已分配的处理机分配给另一个进程。

    这样做的好处是:可以放置一个长进程长时间地占用处理机,确保处理机能为所有进程提供更为公平的服务。

    抢占的主要原则有三:

    • 优先权原则。允许优先级高的进程抢占优先级低的进程。
    • 短进程优先原则。允许新到的短进程抢占长进程。
    • 时间片原则。进程按时间片轮转执行,时间片用完则被抢占。
3.3.2 轮转调度算法(RR 调度算法)

​ 最简单、常用的基于时间片的调度算法,分配方式非常公平。

(1)基本原理

​ 根据 FCFS 策略将所有接续进程排列成一个就绪队列,设置一定的时间间隔并产生中断,激活进程调度程序进行调度,每次调出队首进程并执行一个时间片。

(2)进程切换时机

  1. 一个时间片尚未用完,当前进程已经完成

    立即激活调度程序,将其从就绪队列中删除,再从队首调度新的进程运行,并启动一个新的时间片。

  2. 一个时间片用完时

    计时器中断处理程序被激活。若进程尚未完成,将其插入就绪队列的队尾。

(3)时间片大小的确定

  • 时间片过小

    频繁执行进程调度和上下文切换,导致系统开销增加。

  • 时间片过长

    RR 算法退化为 FCFS 算法,无法满足短作业和交互式用户的要求。

​ 合理的时间片大小可以是:略大于一次典型的交互所需要的是时间。

3.3.3 优先级调度算法

(1)算法类型

  1. 非抢占式优先级调度算法

    一旦把处理机分配给就绪队列中优先级最高的进程后,该进程边一直执行下去直到完成,或因某事件而主动放弃处理机,才可以将处理机重新分配给优先级其次进程。

  2. 抢占式优先级调度算法

    只要出现 另一个优先级最高的进程,便立即将处理机分配给它。

(2)优先级类型

  1. 静态优先级

    优先级利用某一范围内的一个整数来表示(0 ~ 255),在创建进程时已经确定,不发生改变。

    确定优先级大小的依据有三:

    • 进程类型
    • 进程对资源的需求
    • 用户要求

    优点:简单易行,系统开销小

    缺点:可能优先级低的进程长期没有被调度

  2. 动态优先级

    优先级会随着进程的推进或等待时间的增加而改变。这种优先级方式可以放置一个长作业长期地垄断处理机。

3.3.4 多队列调度算法

​ 将系统中的进程就绪队列拆分为若干个,不同类型或性质的进程固定分配在不同的就绪队列,采用不同的调度算法。一个就绪队列中的进程可以设置不同的优先级,不同队列本身也可以设置不同的优先级。

​ 这种算法能够一定程度上满足不同用户对进程调度策略的不同要求。

3.3.5 多级反馈队列调度算法

​ 这种算法不必事先知道进程所需的执行时间,是目前公认的一种较好的进程调度算法。

(1)调度机制

  1. 设置多个就绪队列

    系统中设置多个就绪队列,队列的优先级按顺序逐次降低,队列中进程的时间片逐渐升高。

  1. 每个队列都采用 FCFS 算法

    新进程进入内存后,首先将其放入第一个队列的末尾,按照 FCFS 原则等待调度。若第一个时间片结束尚未完成,则调度程序将其转入第二队列的末尾等待调度。若第二个时间片结束尚未完成,则转入第三队列的末尾等待调度…

    每个队列中采取 RR 方式运行。

  2. 按队列优先级调度

    调度程序调度最高优先级队列中的进程,仅当队列中为空时,才调度下一优先级队列中的进程。

    如果处理机正在第 i 队列处理进程,此时有新进程进入更高优先级的队列中,则应把正在运行的进程放回到第 i 队列的末尾,开始处理新进程。

(2)调度算法的性能

​ 对于以下类型的用户,都能较好地满足需求:

  • 终端型用户(多属于交互型作业,通常作业较小)
  • 短批处理作业用户
  • 长批处理作业用户
3.3.6 基于公平原则的调度算法

​ 以上的几种调度算法只保证了优先运行,但并不保证作业占用了多少处理机的时间,也为考虑到调度的公平性,下面是两种相对公平的调度算法:

(1)保证调度算法

​ 该算法并不保证优先运行,而是保证明确的性能。对于 N 个相同类型的进程同时运行,保证每个进程获得近似相同的处理机时间,即 1 / N。系统中必须实现这样一些功能:

  1. 跟踪计算每个进程创建以来已经执行的处理时间 t
  2. 计算每个进程应获得的处理机时间 T,即自创建以来的时间 / n
  3. 计算进程获得处理机时间的比率 α \alpha α,即 t / T
  4. 比较各进程的 α \alpha α,选择比率最小的进程并分配处理机,使其一直运行,直到超过比率第二小的进程比率为止

(2)公平分享调度算法

​ 上述算法保证了对进程的公平,但如果各个用户所拥有的的进程数不同,那么对用户而言就不公平。拥有进程数多的用户反而得到的处理机时间相对较少。

​ 假设用户 1 拥有 4 个进程 A、B、C、D,用户 2 只有 1 个进程 E。为了保证两个用户能获得相同的处理机时间,必须做出相应的处理,即强制执行以下的调度序列:

A E B E C E D E A E B E C E D E ...

​ 如果希望用户 1 获得处理机的时间是用户 2 的两倍,则调度序列应为:

A B E C D E A B E C D E A B E C D E A B E C D E ...
3.4 实时调度
3.4.1 实现实时调度的基本条件

(1)提供必要的信息

  1. 就绪时间
  2. 开始截止时间和完成截止时间
  3. 处理时间
  4. 资源要求
  5. 优先级

(2)系统处理能力强

​ 在实时系统中,若处理机的处理能力不够强,则有可能因处理机忙不过来,致使某些实时任务不能得到及时处理。

​ 若系统中有 m 个周期性的硬实时任务 HRT,处理时间表示为 Ci,周期时间为 Pi,处理机数量为 N,必须满足下面的限制条件才是可以调度的:
∑ i = 1 m C i P i ≤ N \sum_{i=1}^{m}{C_{i}\over P_{i}}\le N i=1mPiCiN
​ 这里没有考虑任务切换的时间,因此限制条件还应该更苛刻一些。

(3)采用抢占式调度机制

(4)具有快速切换机制

​ 具体分为:

  1. 对中断的快速响应能力
  2. 快速的任务分派能力
3.4.2 实时调度算法的分类

(1)非抢占式调度算法

  1. 非抢占式轮转调度算法

    响应时间为数秒至数十秒,可以用于要求不太严格的实时控制系统。

  2. 非抢占式优先调度算法

    为实时任务赋予较高的优先级。

    在精心处理后可能使响应时间减少到数秒至数百毫秒,可以用于具有一定要求的实时控制系统。

(2)抢占式调度算法

  1. 基于时钟中断的抢占式优先级调度算法

    在某实时任务到达后,如果优先级更高,并不立即抢占处理机,而是等到时钟中断发生时再抢占。

    调度延迟可以降为几十至几毫秒,可用于大多数的实时系统。

  2. 立即抢占的优先级调度算法

    要求操作系统具有快速响应外部事件中断的能力。一旦出现外部中断,只要当前任务未处于临界区,便能立即剥夺其执行,将处理机分配给请求中断的紧迫任务。

    调度延迟降低到了几毫秒至数百微妙,甚至更低。

3.4.3 最早截止时间优先算法(EDF)

​ 根据任务的截止时间确定任务的优先级,任务的截止时间越早,优先级越高,最早截止时间的任务排列在队首。

​ 该算法既可用于抢占式调度方式,也可用于非抢占式调度方式。

(1)非抢占式调度方式用于非周期实时任务

​ 以下图为例。该例中具有四个非周期任务,它们先后到达。系统先调度任务 1 执行,在任务 1 执行期间,任务 2、3 又先后到达。由于任务 3 的开始截止时间早于任务 2 的,故系统在任务 1 后将先调度任务 3 执行。在此期间又到达作业 4,其开始截止时间仍是早于任务 2 的,故在任务 3 执行完后,系统又先调度任务 4 执行,最后才调度任务 2 执行。

(2)抢占调度方式用于周期实时任务

​ 假设有两个周期任务 A、B,周期时间分别为 20 ms 和 50 ms,每个周期的处理时间分别为 10 ms 和 25 ms。

​ 上图示出了将最早截止时间(最后期限)优先算法用于抢占调度的示意图。图中的第一行示出了两个任务的到达时间、截止时间和执行时间图。其中任务 A 的到达时间为0、20ms、40 ms …,任务 A 的最后期限为 20 ms、40ms、60 ms…,任务 B 的到达时间为0、50ms、100 ms …,任务 B 的最后期限为 50ms、100 ms…。

​ 为了说明通常的优先级调度不能适用于实时系统,该图特增加了第二和第三行。在第二行中,假定任务 A 具有较高的优先级,所以在 t = 0 ms 时,先调度 A1 执行,在 A1完成后(t = 10 ms)才调度 B1 执行。在 t = 20 ms 时,又重新调度 A2 执行,在 t = 30 ms 时,A2 完成,又调度 B1 执行。在 t = 40 ms 时,又调度 A3 执行,在 t = 50 ms 时,虽然 A3 已完成,但 B1 已错过了它的最后期限。这说明利用通常的优先级调度已经失败。第三行与第二行类似,只是假定任务 B 具有较高的优先级。

​ 第四行是采用最早截止时间优先算法的时间图。在 t = 0时,A1 和 B1 同时到达,由于 A1 的截止时间比 B1 早,故调度 A1 执行。在 t = 10 时,A1 完成又调度 B1 执行。在 t = 20 时,A2 到达,由于 A2 的截止时间比 B2 早,B1 被中断而调度 A2 执行。在 t = 30 时,A2 完成,又重新调度 B1 执行。在 t = 40 时,A3 又到达,但 B1 的截止时间要比 A3 早,仍应让 B1 继续执行直到完成(t = 45),然后再调度 A3 执行。在 t = 55 时,A3 完成又调度 B,执行。在该例中,利用最早截止时间优先算法可以满足系统的要求。

3.4.4 最低松弛度优先算法(LLF)

​ 该算法在确定任务的优先级时,根据的是任务的紧急(松弛)程度:

松弛度 = 必须完成时间 - 本身运行的时间 - 当前时间

​ 该算法主要用于可抢占调度方式中。

​ 如上图,假如在一个实时系统中有两个周期性实时任务 A 和 B,任务 A 要求每 20 ms 执行一次,执行时间为 10 ms,任务 B 要求每 50 ms 执行一次,执行时间为 25 ms。由此可知,任务 A 和 B 每次必须完成的时间分别为:A1、A2、A3、…和 B1、B2、B3、…。为保证不遗漏任何一次截止时间,应采用最低松弛度优先的抢占调度策略。

​ 在刚开始时(t = 0),A1 必须在 20 ms 时完成,而它本身运行又需 10 ms,可算出 A1 的松弛度为 10 ms。B1 必须在 50 ms 时完成,而它本身运行就需 25 ms,可算出 B1 的松弛度为 25 ms,故调度程序应先调度 A1 执行。在 t = 10 ms 时,A2 的松弛度可按下式算出:
A 2 的松弛度 = 必须完成时间-其本身的运行时间-当前时间 = 40 m s − 10 m s − 10 m s = 20 m s \begin{aligned} A_{2}\text{的松弛度} = &\text{必须完成时间-其本身的运行时间-当前时间}\\ =&40 ms - 10 ms- 10 ms \\ =& 20 ms \end{aligned} A2的松弛度===必须完成时间-其本身的运行时间-当前时间40ms10ms10ms20ms
​ 类似地,可算出 B 的松弛度为 15 ms,故调度程序应选择 B1 运行。在 t3 = 30 ms 时,A2 的松弛度已减为 0(即 40-10-30),而 B1 的松弛度为 15 ms(即 50-5-30),于是调度程序应抢占 B1 的处理机而调度 A2 运行。在 t4 = 40 ms 时,A3 的松弛度为 10 ms(即 60-10-40),而 B1 的松弛度仅为 5 ms(即 50-5-40),故又应重新调度 B1 执行。在 t5 = 45 ms 时,B1 执行完成,而此时 A3 的松弛度已减为 5 ms(即 60-10-45),而 B2 的松弛度为 30 ms(即100-25-45),于是又应调度 A3 执行。在 t6 = 55 ms 时,任务 A 尚未进入第 4 周期,而任务 B 已进入第 2 周期,故再调度 B2 执行。在 t7 = 70 ms 时,A4 的松弛度已减至 0 ms(即 80-10-70),而 B2 的松弛度为 20 ms(即 100-10-70),故此时调度程序又应抢占 B2 的处理机而调度 A4 执行。

3.4.5 优先级倒置

(1)优先级倒置的形成

​ 在系统中可能产生“优先级倒置”的现象,即高优先级的进程(线程)被低优先级的进程(线程)延迟或阻塞。

​ 假如有三个挖完全独立的进程 P1、P2、P3,优先级逐次降低,P1 和 P3 通过共享的一个临界资源进行交互:

P1:
	...
    P(mutex);
    CS - 1;          // 临界资源使用
    V(mutex);

P2:
    ...
    program2;
    ...
        
P3:
    ...
    P(mutex);
    CS - 3;          // 临界资源使用
    V(mutex);

​ P3 最先执行,进入临界区 CS - 3。

​ 在时刻 a,P2 就绪,会抢占 P3 的处理机。

​ 在时刻 b,P1 就绪,抢占了P2 的处理机。

​ 在时刻 c,P1 执行 P(mutex) 操作,试图进入临界区 CS - 1,因为 CS 已被 P3 占用,因此 P1 阻塞,P2 继续执行。

​ 在时刻 d,P2 运行结束,P3 继续运行。

​ 在时刻 e,P3 退出临界区。因为 P1 优先级高,所以唤醒 P1,抢占 P3 的处理机。

​ 在这个过程中,因为 P1 和 P3 共享资源,导致低优先级的进程 P2 反而在高优先级进程 P1 之前运行,从而延长了 P1 被阻塞的时间,这种时间是不可预知和无法限制的。这种“优先级倒置”的现象不应出现在实时系统中。

(2)优先级倒置的解决办法

  1. 简单的方法——规定 P3 在进入临界区后处理机不允许被抢占。

    如果 P3 的临界区非常长,高优先级进程 P1 仍然会等待很长时间,效果无法令人满意。

  2. 使用的方法——赋予动态优先级

    当高优先级进程 P1 要进入临界区,而临界区又被低优先级进程 P3 抢占时。做出以下处理:

    • P1 被阻塞
    • P3 继承 P1 的优先级,直到 P3 退出临界区

    这样就可以防止优先级在 P3 和 P1 之间的进程插入进来抢占 CPU。如下图:

3.5 死锁概述
3.5.1 资源问题

​ 可以引起死锁的主要是临界资源。

(1)可重用性资源和消耗性资源

  1. 可重用性资源

    一种可供用户重复使用多次的资源。具有以下性质:

    • 每个可重用性资源中的单元只能分配给一个进程使用,不允许共享
    • 按照“请求—使用—释放”的顺序使用资源
    • 系统中每一类可重用性资源的单元数目是相对固定的,进程在运行期间不能创建和删除它

    对资源的请求和释放通常是利用系统调用来实现的,计算机系统中大多数资源都属于可重用资源。

  2. 消耗性资源

    又称临时性资源,在进程运行期间,由其动态创建和消耗。具有以下性质:

    • 每一类可消耗性资源的单元数目可以动态变化
    • 进程运行时,可以不断创造可消耗性资源,也可以请求若干个可消耗性资源单元进行消耗,不返回给资源类中。

    消耗性资源通常由生产者进程创建,消费者进程消耗。最典型的例子就是进程间通信的消息。

(2)可抢占性资源和不可抢占性资源

  1. 可抢占性资源

    • 时间抢占:优先级高的进程抢占优先级低的进程的处理机
    • 空间抢占:内存紧张时,将进程从内存调出到外存

    可见,CPU 和主存均属于可抢占性资源,这类资源不会引起死锁

  2. 不可抢占性资源

    一旦系统把资源分配给进程后,就不能将它强行收回,只能在进程用完后自行释放。

    例如,光盘、磁带机、打印机等。

3.5.2 计算机系统中的死锁

(1)竞争不可抢占性资源引起死锁

​ 例如,有两个进程 P1 和 P2,都准备写两个文件 F1 和 F2,二者都属于可重用和不可抢占性资源。进程 P1 先打开 F1,后打开 F2;进程 P2 先打开 F2,后打开 F1
P 1 P 2 … … Open(f 1 , w); Open(f 2 , w); Open(f 2 , w); Open(f 1 , w); \begin{array}{c} \text{P}_{1} & \text{P}_{2}\\ \dots & \dots\\ \text{Open(f}_{1},\text{w);} & \text{Open(f}_{2},\text{w);}\\ \text{Open(f}_{2},\text{w);} & \text{Open(f}_{1},\text{w);} \end{array} P1Open(f1,w);Open(f2,w);P2Open(f2,w);Open(f1,w);
​ 如果在 P1 打开 F1 的同时, P2 去打开 F2,每个进程都占有一个打开的文件,此时就可能出现问题。因为当 P1 试图去打开 F2,而 P2 试图去打开 F1 时,这两个进程都会因文件已被打开而阻塞,它们希望对方关闭自己所需要的文件,但谁也无法运行,因此这两个进程将会无限期地等待下去,而形成死锁。

资源分配图:

​ 方块代表可重用资源(文件),圆圈代表进程。

​ 箭头从进程指向文件,表示进程请求资源(打开文件);箭头从资源只想进程,表示资源已被分配给该进程(已被进程打开)。

​ 上图形成了一个回路,说明进入了死锁状态。

(2)竞争消耗性资源引起死锁

​ 例如,m1、m2 和 m3 是可消耗资源,进程 P1、P2 和 P3 的动作如下所示:

P1:  ...receive(p3, m3);    send(p2, m1);
P2:  ...receive(p1, m1);    send(p3, m2);
P3:  ...receive(p2, m2);    send(p1, m3);

​ 三个进程都先等待对方发送消息,进入死锁。

(3)进程推进顺序不当引起死锁

​ 进程运行中,对资源进行申请和释放的顺序也会引起死锁。例如,系统中只有一台打印机 R1 和一台磁带机 R2,可以供进程 P1 和 P2 共享。

​ 若并发进程 P1 和 P2 按图中曲线 ④ 所示的顺序推进,它们将进入不安全区 D 内。此时 P1 保持了资源 R1,P2 保持了资源 R2,系统处于不安全状态。此刻,如果两个进程继续向前推进,就可能发生死锁。

​ 例如,当 P1 运行到 P1:Request(R2)时,将因 R2 已被 P2 占用而阻塞;当 P2 运行到 P2:Request(R1)时,也将因 R1 已被 P1 占用而阻塞,于是发生了进程死锁。这样的进程推进顺序就是非法的。

3.5.3 死锁的定义、必要条件和处理方法

(1)死锁的定义

​ 如果一组进程中的每一个进程都在等待仅由该组进程中的其它进程才能引发的事件,那么该组进程是死锁的(Deadlock)。

(2)产生死锁的必要条件

​ 产生死锁必须具备以下四个必要条件,缺少一个都不会发生死锁:

  1. 互斥条件

    在一段时间内,某资源只能被一个进程占用。其它进程只能等待该进程用毕释放。

  2. 请求和保持条件

    进程已经保持了一些资源,并且提出新的请求。

    请求的资源被占用,但进程并不释放自己已占用的资源。

  3. 不可抢占条件

    进程已获得的资源不可被抢占,只能被用毕释放。

  4. 循环等待条件

    存在一个“进程—资源”的循环链。

    进程集合 {P0, P1, … , Pn } 中,P0 等待 P1 占用的资源,P1 等待 P2 占用的资源,…,Pn 等待 P0 占用的资源。

(3)处理死锁的方法

  1. 预防死锁

    限制某些条件,从而破坏产生死锁的四个必要条件之一或更多。

    该方法容易实现,简单直观,被广泛使用。

  2. 避免死锁

    在资源的动态分配过程中,用某种方法防止系统进入不安全状态。

  3. 检测死锁

    不采取任何限制性措施,允许发生死锁,但可以及时检测出死锁的发生,然后采取相应的措施解脱死锁。

  4. 解除死锁

    系统已经发生死锁时,采取相应措施从其中解脱出来。

    例如,撤销一些进程,回收资源。

​ 上述方法中,从方法 1 到方法 4:

  • 防范程度逐渐减弱
  • 资源利用率和并发程度逐渐提高
3.6 预防死锁

​ 由于互斥条件是非共享设备所必须的,不仅不能改变,还需要保证。因此主要是破坏后面三个条件。

3.6.1 破坏“请求和保持”条件

(1)第一种协议

​ 该协议规定,所有进程开始运行之前,必须一次性地申请其在整个运行过程中所需的全部资源。

  • “请求”条件被破坏:进程在整个运行期间,不会再提出新的资源要求
  • “保持”条件被破坏:未能拥有全部资源的进程无法运行,自然也无法“保持”资源
  1. 优点:简单易行、非常安全
  2. 缺点:
    • 资源被严重浪费,恶化资源利用率
    • 进程经常发生饥饿现象

(2)第二种协议

​ 该协议规定,允许一个进程只获得初期运行所需的资源后便开始运行。进程运行过程中逐步释放自己用完的资源,然后再请求新的资源。

​ 可以使进程更快地完成任务,提高设备的利用率,减少进程饥饿的几率。

3.6.2 破坏“不可抢占”条件

​ 协议中规定,进程提出新的资源请求而不能得到满足时,它必须释放已经保持的所有资源,这些资源以后如果需要时再继续申请。

​ 这种规定相当于不可抢占的资源被抢占了,从而破坏了“不可抢占”的条件。

  • 缺点:
    • 实现复杂,代价较大
    • 可能因反复申请释放资源使得进程被推迟执行
    • 延长进程的周转时间,增加了系统开销,降低吞吐量
3.6.3 破坏“循环等待”条件

​ 对系统中所有资源类型进行排序,每个资源类型赋予唯一的序号。并且规定:

  • 进程必须按照序号递增的方式请求资源;
  • 如果需要多个同类资源,则必须一起请求;
  • 如果需要请求序号低的资源,必须将序号高于其的资源全部释放。

​ 采用这种方法时,对于序号的分配十分重要。通常,进程总是先输入程序和数据,继而运行计算,最后输出结果。因此,输入设备的序号应较低,输出设备的序号应较高。

  1. 优点:相对前两种方法,资源利用率和系统吞吐量有明显改善
  2. 缺点:
    • 系统中序号排序需要相对稳定,因此对于新设备类型的加入便不好处理
    • 作业使用各类资源的顺序与系统规定的顺序可能不同,造成资源浪费
    • 这种申请资源的顺序会对用户编程产生较大的限制
3.7 避免死锁
3.7.1 安全状态

(1)安全状态

​ 指系统能按照某种进程推进顺序 (P1, P2, … ,Pn) 为每个进程 Pi 分配其所需的西苑,直至满足每个进程对资源的最大需求,每个进程都能顺利完成。

​ 如果系统无法找到满足上述条件的这样一个序列 (P1, P2, … ,Pn),则系统会处于不安全状态。这个序列 (P1, P2, … ,Pn) 称为安全序列。

  • 并非所有不安全状态都会转为死锁状态,但还是有可能的
  • 如果系统处于安全状态,则一定不会死锁

(2)安全状态的例子

​ 假定有三个进程 P1,P2,P3,共有 12 台磁带机。在 T0 时刻,各个进程的磁带机需求量和已分配数如下表:
T 0 : 进程 最大需求 已分配 可用 P 1 10 5 3 P 2 4 2 P 3 9 2 T_{0}:\begin{array}{c} \hline \text{进程} & \text{最大需求} & \text{已分配} & \text{可用}\\ \hline P_{1} & 10 & 5 & 3\\ P_{2} & 4 & 2 & \\ P_{3} & 9 & 2 &\\ \hline \end{array} T0:进程P1P2P3最大需求1049已分配522可用3
​ 经分析发现,此时刻系统是安全的,因为存在一个安全序列 (P2, P1, P3),系统只要按照这个序列分配资源,就可以使得每个进程都顺利完成。如下表:
T 1 : 进程 最大需求 已分配 再分配 可用 P 1 10 5 − 1 P 2 4 2 2 1 P 3 9 2 − 1 T_{1}:\begin{array}{c} \hline \text{进程} & \text{最大需求} & \text{已分配} & \text{再分配} & \text{可用}\\ \hline P_{1} & 10 & 5 & - & 1\\ P_{2} & 4 & 2 & 2 & 1 \\ P_{3} & 9 & 2 & - & 1\\ \hline \end{array} T1:进程P1P2P3最大需求1049已分配522再分配2可用111

T 2 : 进程 最大需求 已分配 再分配 可用 P 1 10 5 5 0 P 2 d o n e d o n e d o n e 0 P 3 9 2 − 0 T_{2}:\begin{array}{c} \hline \text{进程} & \text{最大需求} & \text{已分配} & \text{再分配} & \text{可用}\\ \hline P_{1} & 10 & 5 & 5 & 0\\ P_{2} & done & done & done & 0 \\ P_{3} & 9 & 2 & - & 0\\ \hline \end{array} T2:进程P1P2P3最大需求10done9已分配5done2再分配5done可用000

T 3 : 进程 最大需求 已分配 再分配 可用 P 1 d o n e d o n e d o n e 3 P 2 d o n e d o n e d o n e 3 P 3 9 2 7 3 T_{3}:\begin{array}{c} \hline \text{进程} & \text{最大需求} & \text{已分配} & \text{再分配} & \text{可用}\\ \hline P_{1} & done & done & done & 3\\ P_{2} & done & done & done & 3\\ P_{3} & 9 & 2 & 7 & 3\\ \hline \end{array} T3:进程P1P2P3最大需求donedone9已分配donedone2再分配donedone7可用333

(3)安全状态转向不安全状态

​ 如果在 T0 时刻后 P3 又请求一台磁带机:
T 0 ′ : 进程 最大需求 已分配 可用 P 1 10 5 2 P 2 4 2 P 3 9 3 T_{0}':\begin{array}{c} \hline \text{进程} & \text{最大需求} & \text{已分配} & \text{可用}\\ \hline P_{1} & 10 & 5 & 2\\ P_{2} & 4 & 2 & \\ P_{3} & 9 & 3 &\\ \hline \end{array} T0:进程P1P2P3最大需求1049已分配523可用2
​ 就会发现,找不到一个满足条件的安全序列。从这一时刻开始,系统便进入了不安全状态。

3.7.2 利用银行家算法避免死锁

​ 最有代表性的避免死锁的算法是 Dijkstra 的银行家算法。该算法原本是为了银行系统设计的,为了确保银行在发放现金贷款时,不会发生不能满足所有客户需要的情况。在 OS 中也可用来避免死锁。

​ 使用这一算法时,进程必须申明在运行过程中,可能需要每种资源类型的最大单元数目。系统会首先确定是否有足够的资源分配,再进一步计算分配后是否会进入不安全状态。

(1)数据结构

  1. 可利用资源向量 Available

    一个含有 m 个元素的数组,每一个元素代表一类可利用的资源数目,初始值为系统中所有资源的各类数目。
    A v a i l a b l e 资源  R 1 资源  R 2 … 资源  R m 第 1 类资源的数目 第 2 类资源的数目 … 第 m 类资源的数目 Available\\ \begin{array}{c} \hline \text{资源 }R_{1} & \text{资源 }R_{2} & \dots & \text{资源 }R_{m}\\ \text{第 1 类资源的数目} & \text{第 2 类资源的数目} & \dots & \text{第 m 类资源的数目}\\ \hline \end{array} Available资源 R1 1 类资源的数目资源 R2 2 类资源的数目资源 Rm m 类资源的数目

  2. 最大需求矩阵 Max

    一个 n × m 矩阵,定义了系统中 n 个进程对 m 类资源的最大需求。
    M a x 进程 \ 资源最大需求量 资源  R 1 资源  R 2 … 资源  R m 进程 1 M a x ( 1 ,   1 ) M a x ( 1 ,   2 ) … M a x ( 1 ,   m ) 进程 2 M a x ( 2 ,   1 ) M a x ( 2 ,   2 ) … M a x ( 2 ,   m ) … … … … … 进程 n M a x ( n ,   1 ) M a x ( n ,   2 ) … M a x ( n ,   m ) Max\\ \begin{array}{c | c | c | c | c} \hline \text{进程}\backslash \text{资源最大需求量} & \text{资源 }R_{1} & \text{资源 }R_{2} & \dots & \text{资源 }R_{m}\\ \hline \text{进程 1} & Max(1,\ 1) & Max(1,\ 2) & \dots & Max(1,\ m)\\ \text{进程 2} & Max(2,\ 1) & Max(2,\ 2) & \dots & Max(2,\ m)\\ \dots & \dots & \dots & \dots & \dots\\ \text{进程 n} & Max(n,\ 1) & Max(n,\ 2) & \dots & Max(n,\ m)\\ \hline \end{array} Max进程\资源最大需求量进程 1进程 2进程 n资源 R1Max(1, 1)Max(2, 1)Max(n, 1)资源 R2Max(1, 2)Max(2, 2)Max(n, 2)资源 RmMax(1, m)Max(2, m)Max(n, m)

  3. 分配矩阵 Allocation

    一个 n × m 矩阵,定义了系统中 n 个进程已获得的各类资源数。
    A l l o c a t i o n 进程 \ 进程已分配的资源数 资源  R 1 资源  R 2 … 资源  R m 进程 1 A l l o c a t i o n ( 1 ,   1 ) A l l o c a t i o n ( 1 ,   2 ) … A l l o c a t i o n ( 1 ,   m ) 进程 2 A l l o c a t i o n ( 2 ,   1 ) A l l o c a t i o n ( 2 ,   2 ) … A l l o c a t i o n ( 2 ,   m ) … … … … … 进程 n A l l o c a t i o n ( n ,   1 ) A l l o c a t i o n ( n ,   2 ) … A l l o c a t i o n ( n ,   m ) Allocation\\ \begin{array}{c | c | c | c | c} \hline \text{进程}\backslash \text{进程已分配的资源数} & \text{资源 }R_{1} & \text{资源 }R_{2} & \dots & \text{资源 }R_{m}\\ \hline \text{进程 1} & Allocation(1,\ 1) & Allocation(1,\ 2) & \dots & Allocation(1,\ m)\\ \text{进程 2} & Allocation(2,\ 1) & Allocation(2,\ 2) & \dots & Allocation(2,\ m)\\ \dots & \dots & \dots & \dots & \dots\\ \text{进程 n} & Allocation(n,\ 1) & Allocation(n,\ 2) & \dots & Allocation(n,\ m)\\ \hline \end{array} Allocation进程\进程已分配的资源数进程 1进程 2进程 n资源 R1Allocation(1, 1)Allocation(2, 1)Allocation(n, 1)资源 R2Allocation(1, 2)Allocation(2, 2)Allocation(n, 2)资源 RmAllocation(1, m)Allocation(2, m)Allocation(n, m)

  4. 需求矩阵 Need

    一个 n × m 矩阵,定义了系统中 n 个进程尚需求的各类资源数。
    N e e d 进程 \ 进程仍需的资源数 资源  R 1 资源  R 2 … 资源  R m 进程 1 N e e d ( 1 ,   1 ) N e e d ( 1 ,   2 ) … N e e d ( 1 ,   m ) 进程 2 N e e d ( 2 ,   1 ) N e e d ( 2 ,   2 ) … N e e d ( 2 ,   m ) … … … … … 进程 n N e e d ( n ,   1 ) N e e d ( n ,   2 ) … N e e d ( n ,   m ) Need\\ \begin{array}{c | c | c | c | c} \hline \text{进程}\backslash \text{进程仍需的资源数} & \text{资源 }R_{1} & \text{资源 }R_{2} & \dots & \text{资源 }R_{m}\\ \hline \text{进程 1} & Need(1,\ 1) & Need(1,\ 2) & \dots & Need(1,\ m)\\ \text{进程 2} & Need(2,\ 1) & Need(2,\ 2) & \dots & Need(2,\ m)\\ \dots & \dots & \dots & \dots & \dots\\ \text{进程 n} & Need(n,\ 1) & Need(n,\ 2) & \dots & Need(n,\ m)\\ \hline \end{array} Need进程\进程仍需的资源数进程 1进程 2进程 n资源 R1Need(1, 1)Need(2, 1)Need(n, 1)资源 R2Need(1, 2)Need(2, 2)Need(n, 2)资源 RmNeed(1, m)Need(2, m)Need(n, m)

​ 上述三个矩阵之间存在以下的关系:
N e e d [ i ,   j ] = M a x [ i ,   j ] − A l l o c a t i o n [ i ,   j ] Need[i,\ j]=Max[i,\ j]-Allocation[i,\ j] Need[i, j]=Max[i, j]Allocation[i, j]
(2)银行家算法

​ 设 Requesti 是进程 Pi 的请求向量。如果 Requesti[j]=K,表示进程 Pi 需要 K 个 Rj 类型的资源。算法按照以下步骤进行:

  1. 如果不满足 Requesti[j] ≤ Need[i, j],则出错;

  2. 如果不满足 Requesti[j] ≤ Available[j],则让进程 Pi 等待;

  3. 尝试分配资源给 Pi,作出以下修改:

    Available[j]=Available[j]-Requesti[j];

    Allocation[i, j]=Allocation[i, j]+Requesti[j];

    Need[i, j]=Need[i, j]-Requesti[j];

  4. 执行安全性算法。若安全,则分配资源给 Pi,否则分配作废,回复原来的状态,让 Pi 等待。

(3)安全性算法

  1. 设置两个向量 Work 和 Finish

    • work

      表示系统在工作期间剩余的各类资源数目,是一个含有 m 个元素的向量

      初始时令 Work=Available

    • Finish

      表示每个进程是否能够完成,是一个含有 n 个元素的向量。

      初始时令 Finish 全为 false,进程能够运行时 变为true

  2. 从进程集合中找到一个满足下列条件的进程

    Finish[i]=false;

    Need[i, j] ≤ Work[j];

    若找到,进入步骤 3,否则进入步骤 4

  3. 执行进程,完成后释放资源:

    Work[j]=Work[j]+Allocation[i, j];

    Finish[i]=true;

    goto step 2;

  4. 检查所有的 Finish[i],如果都为 true,则系统处于安全状态,否则处于不安全状态

3.8 死锁的检测与解除

​ 如果在系统中,既不采取死锁预防措施,也未配有死锁避免算法,系统很可能会发生死锁。在这种情况下,系统应当提供两个算法:

  1. 死锁检测算法
  2. 死锁解除算法
3.8.1 死锁的检测

(1)资源分配图

​ 资源分配图由一组节点 N 和一组边 E 组成,记为 G = (N, E)。

​ 其中:

  • 进程结点 P = {P1, P2, … ,Pn} 和资源结点 R = {R1, R2, … ,Rn} 共同组成结点 N。
  • 对于边 e ∈ \in E:
    • e = {Pi, Rj},表示进程 Pi 请求一个单位的资源 Rj
    • e = {Rj, Pi},表示将一个单位的资源 Rj 分配给进程 Pi

​ 如图表示了 P1 进程已经分得了两个 R1 资源,并又请求一个 R2 资源;P2 进程分得了一个 R1 和一个 R2,资源,并又请求 R1 资源。

(2)死锁定理

​ 将资源分配图进行简化来判断系统是否会死锁:

  1. 找出一个不阻塞且不独立的进程结点 Pi 使其运行,运行结束后消去 Pi 的请求边和分配边,使其孤立。
  2. 重复步骤 1 直到所有进程结点都成为孤立结点。若不能,则无法简化。

死锁定理——S 为死锁状态的充分条件是:当且仅当 S 状态的资源分配图是不可简化的。

​ 注:所有简化顺序将的得到相同的不可简化图。

(3)死锁检测中的数据结构与算法

​ 类似于银行家算法的数据结构:

  1. 可利用资源向量 Available
  2. 记录不占用资源的进程表 L
  3. 剩余的各类资源数目向量 Work
  4. 各个进程请求资源向量 Request
  5. 进程已获得的各类资源数 Allocation

​ 算法如下:

Work = Available;
L = {L_i | Allocation_i == 0 && Request_i == 0};

for (all L_i ∉ L) {
    for (all Request_i <= Work) {
        Work = Work + Allocation_i;
        L_i ∪= L;
    }
}

deadlock = !(L == {P1, P2, ... , Pn});
3.8.2 死锁的解除

(1)解除死锁的方法

  1. 抢占资源

​ 从其他进程剥夺足够数量的资源给死锁进程,以解除死锁状态。

  1. 终止(撤销)进程

    终止(撤销)系统中的一个或多个死锁进程,直至打破循环环路。

    • 终止所有死锁进程

      最简单的方法。但是付出代价很大,有可能进程已经完成了大部分任务,但是被终止后前功尽弃。

    • 逐个终止死锁进程

      终止进程的顺序尤为重要,应该依据代价最小的原则逐个终止。但是代价最小的度量并没有一个准确的答案,以下是一些可考虑的因素:

      1)进程的优先级

      2)进程的执行时间、还需运行的时间

      3)进程已经使用资源的多少、还需使用多少资源

      4)进程是交互式还是批处理式

(2)鸵鸟算法

​ 传说中鸵鸟看到危险就把头埋在地底下。当你对某一件事情没有一个很好的解决方法时,那就忽略它装作看不到。这样的算法称为“鸵鸟算法“。

​ 应用鸵鸟算法时,操作系统允许死锁的发生,这时需要用户手动解除死锁。例如使用任务管理器终止进程。

​ 很多操作系统,例如 UNIX,LINUX 和 windows,处理死锁问题的办法就是忽略它。

  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蔗理苦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值