【操作系统】死锁

317 阅读12分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第17天,点击查看活动详情

资源

资源是单个进程在任何时刻都可以使用的任何东西,能够获得(请求),使用和释放。

  • Preemptible Resources 可抢占资源:
    • 可以从进程中删除没有不良影响
    • 如,CPU,内存
    • 无死锁。
  • Non-preemptible Resources 非抢占资源:
    • 如果不导致计算失败,则无法从其当前所有者处移除的对象。
    • 如果拿走,将导致进程失败。
    • 如磁带机、CD刻录机
    • 可能出现死锁。

资源也可共享(如只读)

当一个进程被拒绝资源请求时,它将进入睡眠状态。

死锁

如果一个进程集合中的每个进程都在等待一个只有该集合中的另一个进程才能引发的事件,则该进程集合将处于死锁状态。

假设进程只有一个线程。 假设没有可能唤醒被阻止进程的中断。 通常,事件是释放当前持有的资源。

如果出现死锁,则所有进程都无法 运行 释放资源 被唤醒

饥饿(或无限期推迟)? 如果一个进程在系统关注其他进程的同时,在很长一段时间内重复延迟,则该进程将无限期延迟。 例如,从逻辑上讲,进程可以继续,但系统从不给它CPU。

例如:假设一个进程持有资源A,并请求资源B。 同时,另一个进程持有B并请求A。 Snipaste_2022-06-12_13-07-36.png

死锁发生四个必要条件:

  • Mutual Exclusion Condition 互斥条件:资源或是分配给1个进程或是可用
  • Hold and Wait Condition 保持和等待条件: 保持已有资源可以请求额外的资源。
  • No Preemption Condition 不可抢占条件:以前授予的资源不能强制抢占,必须由持有它们的程序明确释放。
  • Circular Wait Condition 环路等待条件:存在一组进程{P1,P2,…PN},因此,P1正在等待P2,P2等待P3。。。PN等P1(资源分配图存在环路,防止死锁主要破坏该条件)

死锁建模:

  • 用有向图建模,圆圈代表进程,方形代表死锁
  • 分配给进程A的资源R(资源R指向A)。
  • 进程B正在请求/等待资源S(进程B指向R)。
  • 有向图形成环即代表循环等待成立,死锁,一个资源实例不能发出多个箭头

Snipaste_2022-06-12_13-07-53.png

image.png

如果图形包含循环,每个资源类型有多个实例,可能出现死锁。

处理死锁的方法

  • 忽略该问题,并假装系统中从未发生死锁(鸵鸟算法)。
  • 允许系统进入死锁状态,然后恢复。
    • 死锁检测和恢复
  • 动态回避(谨慎的资源分配)
  • 预防(否定四个必要条件之一)

Deadlock Detection and Recovery

允许系统进入死锁状态,定期运行死锁检测算法,检测到死锁后,运行恢复算法。

Snipaste_2022-06-12_13-11-32.png

资源单实例 Single resource per type:

如果每个资源只有一个实例,则可以通过构造资源分配/请求图并检查周期来检测死锁。

死锁检测算法

  • 1、对于图中的每个节点N,以N为起始节点执行以下5个步骤:
  • 2、将L初始化为空列表,并清出所有有向边标记(L是节点列表)
  • 3、将当前节点添加到L的末尾,检查该节点现在是否在L中出现两次。如果是,则图形包含一个循环(列在L中),算法终止。
  • 4、从给定节点,查看是否有任何未标记的传出弧。如果是,请转至步骤5;如果没有,请转至步骤6。
  • 5、随机选取一个未标记的引出弧并进行标记。然后,按照它转到新的当前节点并转至步骤3。
  • 6、如果此节点是初始节点,则图不包含任何循环,算法终止。否则,我们现在已经走到了死胡同。删除它并返回到上一个节点,将其设为当前节点,然后转至步骤3。

资源多实例 Multiple Resources of Each Type

基于矩阵的算法

  • N个过程,P1到Pn
  • 资源类数量:m
  • Ei:i类资源(1≤ 我≤ m)
  • E:现有资源向量(注意不是已分配)
  • A:可用资源向量
  • C:当前分配矩阵,C的第i行表示进程i已经持有的各种资源数量,CijC_{ij}表示进程i持有的资源j数量
  • R:请求矩阵,R的第i行表示进程i请求的各种资源数量,RijR_{ij}表示进程i请求的资源j数量

Snipaste_2022-06-12_13-35-51.png

前提:

  • 基于向量比较。
  • A ≤ B当且仅当Ai≤Bi 0≤i≤m)
  • 每个进程最初都没有标记,标记了证明能被执行,如果到最后都有进程没标记就是有死锁。

算法:

  • 1、查找未标记进程Pi,对于该进程,R的第i行小于或等于A。(可以满足Pi的请求)
  • 2、如果发现这样的进程,请将第i行C添加到A(Pi完成后,其资源将可用),标记该过程并返回到步骤1。
  • 3、如果不存在这样的进程,则算法终止。未标记的进程(如果有)是死锁进程

Snipaste_2022-06-12_13-42-14.png

如果发现某一列R值的和大于对应E值,代表现有资源不够用,直接判断死锁

Recovery from Deadlock

  • Recovery through preemption 通过抢占恢复。
    • 从其他进程获取资源。
    • 是否可行取决于资源的性质(可抢占资源)。
  • Recovery through rollback 通过回滚进行恢复。
    • 定期检查流程。
    • 进程状态(内存映像和资源状态)写入文件
    • 回滚到此保存状态。
    • 如果发现进程死锁,重新启动该进程。
  • Recovery through killing processes 通过终止进程进行恢复。
    • 破坏死锁的最粗糙但最简单的方法。
    • 如何选择要终止的进程?
      • 杀死死锁循环中的一个进程,然后其他进程获得其资源。
      • 不在死锁循环中终止进程,而是在环外找牺牲品杀死,释放周期中某个进程所需的资源。
      • 最好终止可以重新运行而不会产生不良影响的进程。

Deadlock Avoidance

当进程请求可用资源时,系统必须决定立即分配是否使系统处于安全状态。

特点:

  • 这种技术要求如果发生死锁,系统不会分配资源
  • 非常保守,因为仅仅因为死锁可能发生并不意味着死锁会发生。
  • 要求系统有一些额外的先验信息可用。
  • 最简单、最有用的模型要求每个进程声明其可能需要的每种类型的最大资源数。

示例:

  • 进程A在I1处请求打印机,I3处释放;在I2处请求绘图仪,I4处释放
  • 进程B在I5处请求打印机,I7处释放;在I6处请求绘图仪,I8处释放
  • 资源轨迹:在t点,为避免死锁,应暂停B,直到A请求并释放绘图仪。

image.png

安全和不安全状态

当前状态:哪些流程包含哪些资源。

安全状态(类比可串行化):

  • 系统没有死锁。
  • 存在一个调度顺序,该顺序导致每个进程运行到完成,即使它们都立即请求最大资源。
  • 当进程请求可用资源时,系统必须决定立即分配是否使系统处于安全状态。

安全状态示例:

Snipaste_2022-06-12_14-43-05.png

不安全状态:处于不安全状态并不一定死锁,意味着操作系统不能保证死锁不会发生

在安全状态下,系统可以保证所有进程都将完成,且不用抢占其他进程资源;在不安全状态下,无法提供此类保证。

不安全状态示例:

Snipaste_2022-06-12_14-43-12.png

银行家算法

动态检查资源分配状态,确保系统永远不会进入不安全状态。

银行家算法:在每个请求发生时考虑它,并查看是否授予它会导致安全状态。如果有,则批准该请求;否则,将推迟到以后。

单个资源示例:

image.png

多个资源安全状态的检测:

  • 1、在R(请求矩阵)中查找小于A(可用资源)的新行。如果不存在这样的行,系统最终将死锁==>不安全。(如果有多个进程可供选择,则选择哪个进程无关紧要)。
  • 2、如果存在该行,则进程可能会完成。将该进程(行)标记为结束,并将其所有资源添加到A当中
  • 3、重复步骤1和2,直到所有行都标记==>安全状态,如果有的进程未标记==>非安全状态

多个资源示例: Snipaste_2022-06-12_15-03-26.png

缺点:

  • 银行家算法是保守的——为了安全起见,它减少了并行性。
  • 不太可行
    • 流程很少提前知道其最大资源需求是什么。

    • 进程的数量不是固定的。

    • 原本认为可用的资源可能会突然消失

Deadlock Prevention 死锁预防

对四个条件的破坏。

Attacking the Mutual Exclusion Condition 攻击互斥条件

原则:

  • 避免在非必要时分配资源。
  • 尽可能少的进程实际占用资源。

问题: 并非所有设备都可以后台运行。

示例:通过后台打印打印机输出并将实际打印机仅分配给打印机守护程序。

  • 打印机守护程序不请求其他资源,因此打印机不可能出现死锁。
  • 问题–后台程序是否应该在后台打印所有输出之前开始打印?
  • 假脱机空间有限,因此此决定仍然可能导致死锁。
  • 两个进程各自占用了一半的可用空间,无法继续,因此磁盘上存在死锁

Attacking the Hold and Wait Condition 攻击保持等待条件

为了确保系统中不会出现保持和等待条件,我们必须保证,每当进程请求一个资源时,它不会保留任何其他资源

方法1:要求所有流程在开始执行之前请求其所有资源

  • 如果一切都可用,进程将根据需要进行分配,并可以运行到完成。
  • 如果一个或多个资源正忙,进程将等待。
  • 方法1的问题:
    • 在运行开始时可能不知道所需的资源。能做到这一点,我们可以使用银行家算法。
    • 资源不会得到有效利用(浪费资源)。从磁带读取数据,然后分析一周,然后写回磁带,磁带在整个时间内都无法被其他进程使用。

方法2:进程必须放弃所有资源,然后请求所有立即需要的资源(先放再要)
问题:有些进程必须保留某些资源

Attacking the No Preemption Condition 破坏不可抢占条件

不可抢占资源嗯抢后果难料,但有的资源可以虚拟化变成“可抢占”。

示例:考虑给定打印机的进程

  • 工作进行到一半,现在强行取下打印机。
  • 资源虚拟化:将打印机输出后台打印到磁盘。仅允许打印机守护程序访问真正的打印机。

Attacking the Circular Wait Condition

  • 方法1:一次请求一个资源。请求下一个资源时释放当前资源
  • 方法2:资源按编号排序,必须按资源编号顺序提出请求,如打印机(资源1),绘图机(资源2)
  • 方法3:方法2的变种,不允许请求编号更低的资源

问题:找到一个合适的编号来满足每个人的要求可能很困难/不可能,增加了程序员了解编号的负担。

小结

image.png

死锁活锁与饥饿

  • 死锁:进程被阻止。
  • 活锁:进程运行但没有进展。
  • 死锁和活锁都会导致饥饿。
  • 但饥饿可能还有其他原因: 一个队列从不为空的MLQ调度。内存请求:100MB请求的无限流可能会耗尽200MB请求

饥饿(starvation)

  • 当等待时间给进程推进和响应带来明显影响时,称发生了进程饥饿。
  • 饥饿到一定程度的进程所赋予的使命即使完成 也不再具有实际意义时称该进程被饿死 (starved to death)。
  • 没有时间上界的等待: 排队等待、忙式等待
  • 忙式等待条件下发生的饥饿,称为活锁(live lock).

饥饿 vs 死锁:

  • 死锁进程处于等待状态,忙等待的进程并非处于等待状态(还在轮询), 但却可能被饿死
  • 死锁进程等待永远不会释放的资源, 饿死进程等待可能被释放,但却不会分给自己的资源,其等待时间没有上界
  • 死锁一定发生了循环等待,饿死不然;
  • 死锁至少涉及两个进程, 饿死进程可能只有一个

死锁、活锁、饥饿是包含与被包含的关系。 死锁是活锁的特例,而活锁又是饥饿的特例(死锁<活锁<饥饿

总结

image.png

*回顾:进程管理

进程组件:

  • 定义进程行为的程序。
  • 进程操作的数据及其产生的结果。
  • 提供执行环境的一组资源。
  • 一种状态记录,用于在执行过程中跟踪流程的进度和控制。

Process manager功能:

  • 实现CPU共享(称为调度)。
  • 必须按照某些策略为流程分配资源。
  • 实现进程同步和进程间通信。
  • 实现死锁策略和保护机制。

image.png

Snipaste_2022-06-12_13-07-53.png