【OS操作系统】Operating System 第十一章:死锁与进程通信

OS操作系统系列文章目录

【OS操作系统】Operating System 第一章:操作系统的概述
【OS操作系统】Operating System 第二章:启动、中断、异常和系统调用
【OS操作系统】Operating System 第三章:连续内存分配
【OS操作系统】Operating System 第四章:非连续内容分配
【OS操作系统】Operating System 第五章:虚存技术
【OS操作系统】Operating System 第六章:页面置换算法
【OS操作系统】Operating System 第七章:进程与线程
【OS操作系统】Operating System 第八章:处理机调度
【OS操作系统】Operating System 第九章:同步互斥问题
【OS操作系统】Operating System 第十章:信号量与管程
【OS操作系统】Operating System 第十一章:死锁与进程通信
【OS操作系统】Operating System 第十二章:文件系统



第十一章:死锁与进程通信

死锁问题

  • 死锁现象
    • 流量只在一个方向;
    • 桥的每个部分可以看作一个资源;
    • 如果死锁,可能通过一辆车倒退可以解决(抢占资源和回滚);
    • 如果发生死锁,可能几辆车必须都倒退;
    • 可能发生饥饿;

      在这里插入图片描述

  • 一组阻塞的进程持有一种资源等待获取另一个进程所占有的一个资源;
  • 例子:
    • 系统有2个磁盘驱动器;
    • P1和P2各有一个,都需要另外一个;

系统模型

资源概念

  • 资源类型 R 1 ,   R 2 ,   ⋯   ,   R m R_1,~R_2,~\cdots,~R_m R1, R2, , Rm
    CPU cycles,memory space,IO devices;
  • 每个资源类型 R i R_i Ri W i W_i Wi个实例;
  • 每个进程使用资源如下:
    --- 进程操作     --- 资源状态 ---
    --- request/get --- free resource ---
    ---   use/hold  --- requested/used resource ---
    ---   release   --- free resource ---
    

  • 资源若处于被使用状态,则其它进程就不应该再使用这个资源,有互斥性;如果没有互斥性,就不会产生死锁;

  • 进程使用的资源是有限的,它可以恢复到空闲的情况;

  • 可重复使用的资源

    • 在一个时间内,只有一个进程能使用且不能删除;
    • 进程在获得资源后,最终会再次释放,而其它进程可以重复使用;
    • 处理器,io通道,主和副存储器,设备和数据结构,文件,数据库和信号量都可以看作是资源的一种形式;
    • 如果每个进程拥有一个资源且请求其它资源,就会可能发生死锁;

  • 如何使用资源
    • 创建和销毁,进行资源管理;
    • 在io缓冲区中的中断,信号,消息,信息;
    • 如果接收消息阻塞可能会发生死锁;
    • 可能少见的组合事件会引起死锁;
    • 存在进程管理和调度的过程;

资源分配图

一组顶点V和边E的集合

  • V有两种类型:
    • P = { P 1 ,   P 2 ,   ⋯   ,   P n } P = \{P_1,~P_2,~\cdots,~P_n\} P={P1, P2, , Pn},集合包括系统中的所有进程;
    • R = { R 1 ,   R 2 ,   ⋯   ,   R m } R = \{R_1,~R_2,~\cdots,~R_m\} R={R1, R2, , Rm},集合包括系统中的所有资源;
  • requesting/claiming edge(进程需要资源): P i → R j P_i\rarr R_j PiRj
  • assignmnet/holding edge(资源被进程持有): R j → P i R_j\rarr P_i RjPi

    在这里插入图片描述

死锁判断

  • 情况1:
    不会产生死锁

    在这里插入图片描述

  • 情况2:
    会产生死锁,这个图形成了一个环状的结构(一个大环和一个小环);

    在这里插入图片描述

  • 情况3:
    没有产生死锁,环中分出实例

    在这里插入图片描述

  • 总结:
    死锁一定有环,但有环不一定会产生死锁;
    • 如果图中不包含循环:没有死锁;
    • 如果图中包含循环:
      • 如果每个资源类只有一个实例:一定死锁;
      • 如果存在资源类有几个实例:可能死锁;

死锁特征

  • 死锁出现后会有以下四个特征:
    • 互斥:
      在一个时间只能有一个进程使用资源;
    • 持有并等待:
      进程保持至少一个资源正在等待获取其它进程持有的额外资源;
    • 无抢占:
      一个资源只能被进程自愿释放,进程已经完成了它的任务之后;
    • 循环等待:
      存在等待进程集合 { P 0 ,   P 1 ,   ⋯   ,   P n } \{P_0,~P_1,~\cdots,~P_n\} {P0, P1, , Pn} P 0 P_0 P0正在等待 P 1 P_1 P1所占用的资源, P 1 P_1 P1正在等待 P 2 P_2 P2占用的资源,…, P n − 1 P_{n -1} Pn1在等待 P n P_n Pn所占用的资源, P n P_n Pn在等待 P 0 P_0 P0所占用的资源;
  • 要注意的是,四个特征是死锁出现的必要不充分条件;

死锁处理办法

  • 大致思路:
    • 确保系统永远不会进入死锁状态:
      操作系统的功能会被限制,应用系统无法重复地利用CPU,执行开销很大;
    • 运行系统进入死锁状态,然后恢复:
      判断死锁的开销很大;
    • 忽略这个问题,假装系统中从来没有发生死锁(应用于大多数操作系统):
      靠假设来忽略这个问题,实际操作中的常用方法;

  • 死锁处理办法:
    以下四个方法的约束逐渐降低;
    • Deadlock Prevention(死锁预防);
    • Deadlock Avoidance(死锁避免);
    • Deadlock Detection(死锁检测);
    • Recovery from Deadlock(死锁恢复);

死锁预防&死锁避免

死锁预防 —— 让死锁不会出现

  • 思路:
    只要将前述的四个特征打破其中一个,就不会出现死锁;

  • 预防手段:
    • 互斥:
      令资源不再互斥,但是可能会出现一系列不确定的问题;
    • 占用并等待:
      将条件变大,要拿资源就一次性把全部需要的资源拿去,否则就等待,这样就不会出现死锁;
      但是不同的执行时间段,需要的资源就不同,导致一直占用资源没有使用,会造成系统资源的利用率低;
    • 不抢占:
      直接将进程kill掉,把所需资源抢占过来,手段比较暴力,不合理;
    • 循环等待:
      死锁出现会出现一个环,打破这个环就可以实现死锁的预防;
      如果对资源类型你进行排序,进程按资源顺序进行按需申请,即资源只能往上申请,就不会出现循环的圈;
      但是需要提前将资源排序,且资源利用会不合理;

死锁避免 —— 申请资源前判断


比死锁预防的约束条件放松一点

  • 思路:
    当进程在申请资源的过程中,判断这个申请是否合理,如果会存在死锁的概率,则拒绝请求;

  • 需要系统具有一些额外的先验信息提供
    • 最简单和最有效的模式是要求每个进程声明它可能需要的每个类型资源的最大数目
    • 资源的分配状态是通过限定提供分配的资源数量,和进程的最大需求
    • 死锁避免算法动态检查的资源分配状态,以确保永远不会有一个环形等待状态;
  • 注意:
    不安全状态不一定会导致死锁状态,所以不安全状态包含死锁状态,我们需要安全状态,将是否会形成环作为判断依据;

  • 安全状态
    针对所有的进程,存在一个时间序列,按照这个序列执行,先后顺序执行,所有的进程都可以正常等待需要的资源,正常的结束;
    • 如果系统处于安全状态:无死锁;
    • 如果系统处于不安全状态:可能死锁;
    • 避免死锁:确保系统永远不会进入不安全状态;

      在这里插入图片描述

银行家算法

  • 背景:
    在银行系统中,客户完成项目需要申请带宽的数量有限,每个客户在第一次申请贷款时要声明完成该项目所需的最大资金量,在满足所有贷款要求并完成项目时,客户应及时归还;

  • 前提条件

    • 多个实例;
    • 每个进程都必须能最大限度地利用资源;
    • 当一个进程请求一个资源,就不得不等待;
    • 当一个进程获得所有资源就必须在一段时间内释放它们;

      基于上述条件,银行家算法通过尝试寻求允许每个进程获得地最大资源并结束(把资源返还给系统)的进程请求的一个理想执行时序,以决定一个状态是否安全;
      不存在满足要求的执行时序的状态都是不安全的;

算法设计大致思路

  • 数据结构的设计
    • n
      进程数量;
    • m
      资源类型数量;
    • Max(总需求量):
      n × m n\times m n×m矩阵;如果 M a x [ i , j ] = k Max[i,j] = k Max[i,j]=k,表示进程 P i P_i Pi最多请求资源类型 R j R_j Rjk个实例;
    • Available(剩余空闲量):
      长度为m的向量;如果Available[j] = k,表示有k个类型 R j R_j Rj的资源实例可用;
    • Allocation(已分配量):
      n × m n\times m n×m矩阵;如果Allocation[i,j] = k,表示进程 P i P_i Pi当前分配了k R j R_j Rj的实例;
    • Need(未来需要量):
      n × m n\times m n×m矩阵;如果Need[i,j] = k,表示 P i P_i Pi可能需要k R j R_j Rj实例完成任务;
      Need[i,j] = Max[i,j] - Allocation[i,j]

  • 初始化

    • S1:WorkFinish分别是长度为mn的向量;

      Work = Available;  //当前资源剩余空闲量
      Finish[i] = false;  //所有进程初始化为未完成状态
      
    • S2:找出符合条件的进程i

      • Finish[i] = false:该进程处于未完成状态;
      • N e e d i ⩽ W o r k Need_i\leqslant Work NeediWork:该进程所需资源量能够被满足;

      如果不能找到符合条件的进程,则跳到S4;

    • S3:

      • W o r k = W o r k + A l l o c a t i o n i Work = Work + Allocation_i Work=Work+Allocationi:该进程的资源量需求可以满足,所以配置再回收;
      • Finish[i] = true

      跳到S2;

    • S4:

      • If Finish[i] = true for all i, then the system is in safe state.

      所有进程的FinishTrue,表明系统处于安全状态;

  • 操作:
    初始化一个实际要求向量Request,如果 R e q u e s t i [ j ] = k Request_i[j] = k Requesti[j]=k,表示进程 P i P_i Pi实际需要类型 R j R_j Rj资源的k个实例;

    • S1:如果 R e q u e s t i ⩽ N e e d i Request_i\leqslant Need_i RequestiNeedi成立,则转到S2;否则,提出错误,因为进程实际要求大于其最大要求;
    • S2:如果 R e q u e s t i ⩽ A v a i l a b l e Request_i\leqslant Available RequestiAvailable成立,则转到S3;否则,必须等待,因为给资源不可用;
    • S3:假装给 P i P_i Pi分配需要的资源:生产一个需要判断状态是否安全的资源分配环境,即:
      A v a i l a b l e = A v a i l a b l e − R e q u e s t i Available = Available - Request_i Available=AvailableRequesti;
      A l l o c a t i o n i = A l l o c a t i o n i + R e q u e s t i Allocation_i = Allocation_i + Request_i Allocationi=Allocationi+Requesti
      N e e d i = N e e d i − R e q u e s t i Need_i = Need_i - Request_i Needi=NeediRequesti

      根据返回值作出改变:
    • 如果返回safe,将资源分配给 P i P_i Pi
    • 如果返回unsafe P i P_i Pi必须等待,旧的资源分配状态被恢复;

实例1

  • 系统和进程所拥有的资源如图
    • Max:所有进程需要资源的情况;
    • Need:当前进程需要进程的情况;
    • Available:系统还剩下资源的情况;
    • Allocation:当前进程已经拥有的资源;
    • Resource:当前系统中总资源的个数;

      在这里插入图片描述

  • 当前,只有P2满足情况,其 N e e d 2 ⩽ A v a i l a b l e Need_2\leqslant Available Need2Available,在执行完后,可返回其占有的资源,即 A v a i l a b l e = A v a i l a b l e + A l l o c a t i o n 2 Available = Available + Allocation_2 Available=Available+Allocation2

    在这里插入图片描述

  • 回收资源后,按照顺序,发现P1满足条件,执行相同操作;

    在这里插入图片描述

  • 回收资源之后,剩下的两个进程都可以满足要求,故选哪个都可以,这里先选P3;

    在这里插入图片描述

  • 结论:
    最后我们找到一个序列,即 P 2 → P 1 → P 3 → P 4 P_2\rarr P_1\rarr P_3\rarr P_4 P2P1P3P4,按照这个序列进行执行,所有的进程都可以正常执行并结束,其所需的资源都可以得到满足,这是一个安全序列;

实例2

  • 系统和进程所拥有的资源如图

    在这里插入图片描述

  • 通过银行家算法得到的其中一种安全序列为 P 2 → P 1 → P 3 → P 4 P_2\rarr P_1\rarr P_3\rarr P_4 P2P1P3P4
  • 假设一开始先从 P 1 P_1 P1开始执行,它提出101的资源请求,执行完毕后,剩余资源为:

    在这里插入图片描述

  • 此时系统剩余资源为011,不能满足任何进程的要求,这是一个unsafe状态,银行家算法一开始不会接收 P 1 P_1 P1的101请求;
  • 总结:
    银行家算法的思路是判断当前的资源分配操作是否安全,如果安全则可以执行,如果不安全就不能分配资源;

死锁检测&死锁恢复

死锁检测

  • 背景:
    死锁的检测又将条件放宽一点;
    • 前面的死锁避免是指不会导致死锁出现的方法,如果是出现不安全状态,不会执行;
    • 这里的死锁检测允许系统进程不安全装填,在某个状态判断当前的系统是否出现死锁,如果是,则启动恢复机制了如果没有,则继续执行,将死锁的检测放在系统的运行中;

  • 大致思路:
    • 允许系统进入死锁状态;
    • 死锁检测算法;
    • 恢复机制;

检测原理

  • 将资源分配图中,资源的节点简化,只留下进程;由资源分配图变为进程等待图;然后再判断等待图是否有环,有环代表有可能会死锁;

    在这里插入图片描述

  • 死锁检测算法:
    定期执行对操作系统运行有较大损耗,更多是起调试的作用;而银行家算法需要提前直到进程的所需资源,比较难实现,只能预估,实际使用较少;

    • S1:Work和Finish分别是长度为m和n的向量,初始化:

      • W o r k = A v a i l a b l e Work = Available Work=Available:work为当前空闲资源;
      • F o r    i = 1 , 2 , ⋯   , n    ; i f    A l l o c a t i o n i > 0 For~~i =1, 2,\cdots, n~~;if~~Allocation_i > 0 For  i=1,2,,n  ;if  Allocationi>0, t h e n    F i n i s h [ i ] = f a l s e then~~Finish[i] = false then  Finish[i]=false; o t h e r w i s e    F i n i s h [ i ] = t r u e otherwise~~Finish[i]=true otherwise  Finish[i]=true
        Finish为进程是否结束;
    • S2:找出这样的索引i:
      没有结束的进程,且进程需要的资源小于当前空闲资源;

      • F i n i s h [ i ] = f a l s e Finish[i] = false Finish[i]=false
      • R e q u e s t i ⩽ W o r k Request_i\leqslant Work RequestiWork

      如果没有找到,转到S4;

    • S3:将满足条件的进程的资源释放,且表示为已完成;

      • W o r k = W o r k + A l l o c a t i o n i Work = Work + Allocation_i Work=Work+Allocationi
      • F i n i s h [ i ] = t r u e Finish[i] = true Finish[i]=true

      转到S2;

    • S4:如果有Finish[i]等于false,表示系统处于死锁状态;

      • If Finish[i] = false, for some i 1 ⩽ i ⩽ n 1\leqslant i\leqslant n 1in,系统处于死锁状态;
      • 此外,If Finish[i] = false,表示 P i P_i Pi死锁;

实例1

在这里插入图片描述

实例2

在这里插入图片描述

没有一个进程的需求可以得到满足,死锁会检测出一个环,与银行家算法类似;

  • 算法的使用
    • 何时,使用什么样的频率进行检测依赖于:
      • 死锁多久可能会发生;
      • 多少进程需要被回滚
    • 如果检测算法多次被调用,有可能是资源图有多个循环,所以无法分辨出多个可能死锁进程中的哪些造成死锁;

死锁恢复

  • 终止所有的死锁进程;
  • 在一个时间内终止一个进程直到死锁消除;
  • 终止进程的顺序应该是:
    • 进程的优先级;
    • 进程运行了多久以及需要多少时间才能完成;
    • 进程占用的资源;
    • 进程完成需要的资源;
    • 多少进程需要被终止;
    • 进程是交互还是批处理;

      都存在某种程度上的强制性和不合理性,所以死锁恢复是最后手段;
  • 选择一个受害者:
    最小的成本;
  • 回滚:
    返回到一些安全状态,重启进程到安全状态;
  • 饥饿:
    同一进程可能一直被选座受害者,包括回滚的数量;

IPC概述

基础

IPC:进程间通信

  • 为什么要进行进程间通信:
    进程之间可能要完成一个大的任务,这需要一定的数据沟通和信息传递,保持进程独立性的通信,保证其可以有效的沟通;
  • IPC提供两个操作:
    send messagereceive message
  • 通信的前提:
    在它们之间建立通信链路,通过send/receive交换信息;
  • 通信链路实现:
    物理,例如共享内存、硬件总线;
    逻辑,例如逻辑属性;

间接通信和直接通信

在这里插入图片描述

  • 间接通信
    只需要关注在哪里接收数据,或将数据发送到哪里旧就行,一般是操作系统中的共享数据;
    • 定向从消息队列接收信息:
      • 每个消息队列都有一个唯一的ID;
      • 只有它们共享了一个消息队列,进程才能通信;
    • 通信链路的属性:
      • 只有进程共享一个共同的消息队列,才能建立链路;
      • 链路可以与许多相关联;
      • 每对进程可以共享多个通信链路;
      • 链接可以是单向也可以是双向;
    • 操作:
      • 创建一个新的消息队列;
      • 通过消息队列发送和接收消息;
      • 销毁消息队列
    • 原语的定义:
      • send(A, message):发送消息到队列A;
      • receive(A, message):从队列A接收信息;

  • 直接通信
    • 进程必须正确的命名对方:
      • send(P, message):发送信息到进程P;
      • receive(Q, message):从进程Q接收信息;
    • 通信链路的属性:
      • 自动建立链路;
      • 一条链路恰好对应一对通信进程;
      • 每对进程之间只有一个链接存在;
      • 链接可以是单向的,但一般都是双向的;

阻塞和非阻塞

  • 消息传递可以是阻塞的,也可以是非阻塞的;
  • 阻塞被认为是同步的:
    • Blocking send has the sender block until the message is received;
    • Blocking receive has the receiver block until a message is available;
  • 非阻塞被认为是异步的:
    • Non-blocking send hea the sender send message and continue;
    • Non-blocking receive has the receiver receive a valid message or null;

数据的缓冲

  • 队列的信息被附加到链路,有三种方式:
    • 0 容量 —— 0 messages:
      发送方必须等待接收方(rendezvous);
    • 有限容量 —— n messages的有限长度:
      如果队列满,发送方必须等待;
    • 无限容量 —— 无限长度:
      发送方不需要等待;
  • 无论是哪种情况,但缓冲中没有数据时,接收方都必须等待数据的到来;

信号、管道、消息队列与共享内存

信号

介绍

关注某一种信号,发生了某一种响应之后,可以编写特定的处理函数;效率较高,处理完之后,会回到被打断的函数重新执行;

  • Signal(信号):
    • 软件中断通知事件处理;
    • Examples: SIGFPE, SIGKILL, SIGUSR1, SIGSTOP, SIGCONT
  • 接收到信号是时会发生什么
    • Catch:指定信号处理函数被调用;
    • Ignore:依靠操作系统的默认操作,
      Example: Abort, memory, dump, suspend or resume process
    • Mask:闭塞信号因此不会传送,
      可能是暂时的(当处理同样类型的信号);
  • 不足:
    不能传输要交换的任何数据;

实现

在这里插入图片描述

  • 应用程序针对某种新信号作定点处理,要完成的操作是:
    • 开始的时候,要针对某种信号的handle,把这个作为系统调用发给操作系统;操作系统就知道,当这个进程发出某种信号,就会跳转到预先编写的处理函数中;
    • 操作系统将系统调用返回到用户空间的堆栈进行修改,使得本来是返回调用语句后的条件执行,变成到这个信号处理函数的入口,同时把信号处理函数之后的地址作为栈帧的返回地址;所以要修改应用程序的堆栈;

管道

  • 管道是用来实现数据的交换(以文件的形式操作);
  • 思路:
    将一个文件的输出,重定向到另一个文件得输入,这样就可以完成一系列得操作(重定向符号为”>“);

    在这里插入图片描述

  • 实现:
    • shell进程受到这样一条指令后,会创建两个进程,ls进程和more进程;
    • 同时将ls的输出,接到一个管道中,而不是屏幕上(内存中的一个bffer);
    • 而对于more,不是从键盘接受信息,而是从管道中接受数据,这样就能完成输入输出的重定向功能;
    • 这样就完成了该指令的功能(存在阻塞现象);

  • 特点:
    • 管道是通过父进程帮子进程建立好的一个通道,如果没有父子关系,就不能正常工作;
    • 管道的数据是一种字节流;
    • 有buffer满和buffer空的限制;

消息队列


在这里插入图片描述

  • 特点:
    • 数据是结构化的数据,而不是字节流,传进去的是一个有意义的数据结构;
    • 可以实现多个互不相关的进程完成数据交换;

共享内存


上面两种都是间接通信,共享内存是直接通信的方式;(通过内核、读写内存,实现进程的数据交换)

  • 进程:
    • 每个进程都有私有地址空间;
    • 在每个地址空间内,明确地设置了共享内存段;
  • 优点:
    • 快速,方便地共享数据;
  • 缺点:
    • 必须同步数据访问;

  • 共享内存的实现机制

    在这里插入图片描述
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值