一、死锁问题
1、什么是死锁?
-
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象
-
由于进程的并发情况导致了死锁现象的出现
-
死锁可以描述成几个进程分别不愿意放弃自身的资源,而且要等待其他进程释放资源后才能继续执行
-
通过一个案例说明死锁问题:
(1)图示描述的是一个一次只允许一辆成通过的通道,两侧的车辆要想到达目的地都要走这条通道
(2)如果两侧同时有一辆车进入轨道,并且谁都不愿意退让,就造成了死锁现象
2、用函数抽象出资源:
- 死锁问题可以简化成两方面:需求方、需求的资源
- 函数及变量定义:
(1)使用R1-Rn代表不同的资源类型【比如CPU、存储空间、I/O设备等】
(2)每个资源类型Ri有实例Wi
(3)进程使用资源情况如下:
request/get <-- free resource 【请求资源】
use/hold <-- requested/used resource 【获得并使用资源】
release <-- free resource 【释放资源】
3、资源都有哪些种情况?【此处以重复使用的资源为例】
- 在一个时间内只能被一个进程使用并且不能被删除【进程互斥、不能被动释放资源】
- 进程获得资源后可以释放资源,给其他进程重用
- 如果每个进程拥有一个资源并请求其它资源或接收消息阻塞可能发生死锁
- 这些重复使用的资源可以为 处理器、I/O通道、主副存储器、设备和数据结构等
二、系统模型
4、如何构建进程与资源的占用模型呢?
(1)先分别定义相关的集合和占用、调用关系
Pi --> Rj
代表进程使用资源,Rj --> Pi
代表资源被进程占用
(2)因为要用图示来表述相关的信息,所以给出图示定义
(3)资源分配图案例分析:【分析是否会发生死锁现象】
- 案例一:【进程不会一直占用某个资源】
此处尽管满足了:一个进程占有一部分资源,仍在请求新资源并且新资源被占用;但是并不会发生死锁;
因为当进程P3不会一直占用资源,当其释放资源R3之后,P2进程得到满足开始执行,P2执行结束后释放资源R1,进程P1就得到了满足,所以不会出现死锁现象
- 案例二:【形成了两条资源等待环】
P1需要R1类型的资源,但是被P2占用、P2需要R3类型的资源,但是被P3占用、P3需要R2类型的资源,但是被P1、P2占用 ,所以三个进程陷入了无线等待的情况 >> 产生了死锁
- 案例三:【形成了资源等待环,但是会因为部分进程释放资源】
尽管进程P1、P3之间形成了等待环,但是进程P2、P4是可以正常执行的
当进程P2执行结束之后,会释放出R1资源,进程P1就得到了满足,同理进程P3之后也会得到满足
所以并不会产生死锁的情况
(4)对死锁模型的总结:
- 如果图中不包含循环 >> 不会产生死锁
- 如果图中包含了循环
- 如果每个资源类只有一个实例 >> 产生死锁
- 如果每个资源类有多个实例 >> 不一定会产生死锁【参考案例三】
三、死锁特征
5、死锁有哪些特征?【必要不充分条件】
- 互斥:在一个时间只能有一个进程使用资源
- 持有并等待:进程保持至少一个资源正在等待获取其他进程持有的额外资源
- 无抢占:一个资源只能被进程完成它自身的任务后主动释放资源
- 循环等待:存在等待进程集合{P0,P1, …, Pn},其中进程P0正在等待P1所占用的资源,P1正在等待P2占用的资源,.,PN-1在等待PN所占用资源,PN正在等待PO所占用 的资源。
- 出现死锁现象会具有以上四个特征,但是具有以上四个特征并不一定会导致出现死锁现象
四、死锁的处理方法
6、死锁处理概述:【四种方法的约束强度从上到下不断减弱】
- 实际可以抽象出一下几种解决情况:
(1)确保系统永远不会进入死锁状态,增加了很多限制,但是在一定程度上限制了计算机的功能
(2)运行系统进入死锁状态,然后恢复,死锁的检测和恢复会造成非常大的开销
(3)忽略这个问题,假装系统中从来没有发生死锁【用于大多数操作系统,也包括Unix】
7、死锁预防与死锁避免:
(1)从死锁特征的四个角度去考虑如何预防死锁:
- 互斥:就是让共享资源的占用不再互斥,但是可能会造成一些非预期的结果
- 占用并等待:就是如果一个进程去申请资源,那么首先要确保其不具有其他任何资源【一次性获得所有资源 >> 资源利用率较低、可能导致部分进程饥饿】
$ 如果进程占有某些资源,并请求其它不能被立即分配的资源,则释放当前正占有的资源
$ 被抢占资源添加到资源列表中
$ 只有当它能够获得旧资源以及它请求新的资源,进程可以得到执行
- 无抢占:既然不能主动交枪,那么就直接杀死进程,资源就自动释放了
$ 如果进程占有某些资源,并请求其它不能被立即分配的资源,则释放当前占有的资源
$ 被抢占资源添加到资源列表中
$ 只有当它能够获得就资源以及请求新的资源,进程可以执行
- 循环等待:对于所有资源类型进行排序,并要求每个进程按照资源的顺序进行申请【如果请求低序号的资源无果后,它就不会去请求高需要的资源】
(2)为了避免死锁,会在进程请求资源时调用一些验证信息
-
要求进程事先声明自己需要每个类型资源的最大数目 【如果申请的资源数超过事先规定好的,就拒绝申请资源】
-
资源的分配状态是通过限定提供与分配的资源数量,和进程的最大需求
-
死锁避免算法动态检查的资源分配状态,以确保永远不会有一个环形等待状态。
(3)形成环形等待状态是一种不安全状态,不一定会产生死锁
-
安全序列先后都会获得需要的资源并完成执行
-
如果系统处于安全状态 >> 一定无死锁
-
如果系统处于不安全状态 >> 可能出现死锁
-
避免死锁的作用就是:确保系统永远不会进入不安全状态
(4)避免死锁机制是如何检验下一时刻的状态是否安全的呢?
- 进程需要请求资源用虚线表示【Claim edgePi -> Rj】
- 进程请求资源时,虚线转为实线 【Claim edge >> request edge】
- 当进程被资源释放,实线转为虚线 【assignment edge >> claim edge】
8、什么是银行家算法?
- 银行家算法(Banker’s Algorithm)是一个死锁避免的著名算法,是由艾兹格·迪杰斯特拉在1965年为T.H.E系统设计的一种避免死锁产生的算法。 它以银行借贷系统的分配策略为基础,判断并保证系统的安全运行。
- 银行家算法内容概述:
(1)在银行系统中,客户完成项目需要申请贷款的数量是有限的,每个客户在第一次申请贷款时要声明完成该项目所需的最大资金量,在满足所有贷款要求并完成项目时,客户应及时归还。
(2)银行家在客户申请的贷款数量不超过自己预期约定的最大值时,都应该尽量满足客户的需要。
(3)客户 >> 请求资源的进程、银行家 >> 操作系统、资金 >> 资源
9、如何实现银行家算法?
(1)使用银行家算法的前提
- 存在多个进程、每种资源类型有多个实例
- 每个进程必须最大限度地利用资源
- 当一个进程请求一个资源,不会立刻去分配,而是让进程等待,操作系统去判断请求是否合理
- 当一个进程获得所有的资源就必须在一段有限的时间释放他们【一般为执行完相关操作之后】
(2)定义相关数据结构
- 利用两个整型数n、m分别代表进程数量和资源类型数量
- 利用
n*m
的矩阵 Max 代表需求总量,Max[i, j] = k
,表示进程Pi最多请求资源类型Rj的k个实例 - 利用
n*m
的矩阵 Available 代表剩余资源量,Available[j] = k
表示j类型的资源剩余k个可以被使用 - 利用
n*m
的矩阵 **Allocation **代表已分配量,Allocation[i, j] = k
代表进程Pi已获得k个j类型资源的实例 - 利用
n*m
的矩阵 Need 代表未来还需要资源量,Need[i, j] = k
代表进程Pi还需要k个Rj类型资源的实例才能完成任务
(3)银行家算法的安全状态评估
- 第一步**Work 和Finsh **分别是长度为m和n的向量
//初始化
Work = Available //当前资源剩余空闲量
Finish[i] = false for i -1,2,...,n //线程i没结束
- 第二步找这样的 i:
(a)Finish[i] = false
(b)Needi < Work
没找到这样的 i,转到 4 || 找到了这样的 i,转到 3
- 第三步,进程可以正常结束,并回收资源
Work = Work + Allocation; //进程i的资源需求量小于当前剩余空间资源量
Finish[i] = true //配置给它再回收
转到 2
- 第四步,检查是否处于安全状态
10、基于安全状态判断算法设计银行家算法
- 如果Request < Need,转到步骤2。否则,提出错误条件,因为进程已经超过了其最大要求。
- 如果 Request < Available,转到步骤3。否则,Pi必须等待,因为没有那么多可用的资源
- 假装给Pi分配它需要的资源:【生成一个需要判断状态是否安全的资源分配环境】
Available = Available - Request;
Allocation = Allocation + Request;
Need = Need - Request;
- 如果返回safe,就将资源分配给Pi;否则Pi必须等待,旧的资源分配状态被恢复
11、银行家算法案例分析:
(1)案例一:【资源可以正常分配并使用】
先说明一下各部分都代表什么含义:
**Max **矩阵代表各个进程需要各种资源的最大数量信息、Allocation矩阵代表各个进程已经获得的资源信息
**Need **矩阵代表这些进程还需要的各种资源实例的数量
Resource vector R 代表各种资源初始状态实例的个数
Available vector V 代表当前可以获得的各种资源实例个数
- 根据现有资源,我们可以满足进程P2,P2执行、执行结束后释放资源 >> 各部分的信息如下
根据当前剩余资源情况,我们进程P1、P3、P4都可以执行,选择P1执行,释放资源后如下
选择进程P3继续执行,释放资源后如下
至此,我们得到了一个安全序列 {P2, P1, P3, P4},银行家算法返回safe,这四个线程就会按照安全序列去执行【因为安全序列可以保证所有进程都能得到资源,而且不会出现死锁的情况】
(2)案例二:【剩余资源不能满足任意一个进程执行 >> 不安全(进程不执行完不能释放旧资源)】
12、死锁检测与死锁恢复:
(1)将资源分配图简化成资源等待图 >> 是否有环 >> 是否形成死锁
- 结点代表进程,有向箭头代表资源占用以及请求情况【P1 --> P2就代表某个资源被P2占用、P1请求获得这个资源】
- 定期调用检测算法来搜索图中是否存在循环
- 算法需要执行n^2次操作,n是图中顶点的数目
(2)针对资源类型以及进程抽象出来的数据结构:
-
Available:长度为M的向量代表每种类型可用资源的数量
-
Allocation:一个n*m矩阵定义了当前分配给各个进程每种类型资源的数量。
Allocation[i, j] = k
代表进程 **Pi **拥有资源 **Rj **的实例数目为 k -
Request: 一个n*m矩阵表示各进程的当前请求。
Allocation[i, j] = k
代表进程 **Pi **请求 k 个 Rj 的实例
(3)死锁检测算法:
- 需要定期执行算法,检测系统中是否存在等待资源的环
- 算法需要O(m * n^2) 操作检测是否系统处于死锁状态 >> 开销很大
(4)死锁检测算法案例分析:
- 案例一:
在P0程序执行之前,当前可获得的资源数量为 0 0 0
因为进程P0、P2 已经获得了全部的资源,执行完毕后释放旧资源,当前资源数量为 3 1 3
此时满足进程P1,让其执行,结束后释放资源 >> 资源数量为 5 1 3
继续执行P3、P4,所有进程正常执行,所以不会出现死锁现象
- 案例2:
(5)死锁检测算法开销很大,那我们什么时候去执行呢?
一般很少用于正式的系统中,大多用于开发阶段检测系统是否正确
(6)如果检测出了死锁,那么我们要如何处理呢?
- 一个时间内终止一个进程,直至死锁消失
- 终止进程的顺序如下:
五、进程间通信
1、进程间为什么要通信以及怎样通信?
- 确保进程空间不会被其他不相干的进程访问
- 不使用共享变量进行进程通信
- IPC(进程通信)主要就是两个操作:
(1)send(message) 发送消息【消息的大小可以固定也可以不固定】
(2)receive(message) 接收消息
- 如果连个进程间想要通信:首先要要在他们之间创建通信链路,使用send/receive交换信息
- 那么如何实现通信链路呢?
在物理层面可以通过共享内存、硬件总线等实现;在逻辑层面一般通过逻辑属性实现
2、通信形式主要分为:间接通信和直接通信两种
图示(a)属于间接通信,发送方将信息发送到内核,接收方再从内核接收信息
图示(b)属于直接通信,发送方将信息发送到指定的共享空间,接收方从共享空间读取数据
- 直接通信的操作与通信链路的属性:
(1)进程必须正确命名对方【指定接收方和发送方】
send(P, message) 给进程P发送信息
receive(Q, message) 从进程Q那里接收信息
(2)通信链路的属性
自动建立链路
一条链路恰好对应一对通信进程
每对进程之间只能有一条通信链路存在
链接可以为单向的,但通常为双向的
- 间接通信的操作与通信链路的属性:
(1)定向从消息队列接收消息
每个消息队列都有一个唯一的ID
只有他们共享一个消息队列,进程才能够通信
(2)通信链路的属性
只有进程共享一个共同的消息队列,才能建立链路
连接可以与许多进程相关联
每对进程可以共享多个通信链路
连接可以是单向或双向的
(3)操作
创建一个新的消息队列
通过信息队列发送和接收消息
销毁消息队列
(4)原语定义:
send(A, message) 发送消息到队列A
receive(A, message) 从队列A接收消息
3、消息传递分为两种:
阻塞就是发送方需要等待接收方成功接收到完整的信息;非阻塞就是发送方发完信息就拉到,可能发送结束与接收方收到完整信息间隔很久。
4、通信链路如何缓冲的?
- 队列的消息被附加到链路有三种方式:
(1)0 容量 >> 发送方必须等待接收方
(2)有限容量 >> 如果缓冲队列已经满了,那么发送方必须等待
(3)无限容量 >> 发送方不需要等待【理想情况实际不存在】
5、如何通过信号传递信息?【信号携带数据量比较小,一般就用于通知】
(1)Signal(信号)
- 软件中断通知事件处理
- Examples:SIGFPE,SIGKILL,SIGUSR1,SIGSTOP,SIGCONT
(2)接收到信号时会发生什么
-
Catch:指定信号处理函数被调用
-
Ignore:依靠操作系统的默认操作
- Example:Abort,memory dump,s suspend or resume process ,
-
Mask:闭塞信号因此不会传送
- 可能是暂时的(当处理同样类型的信号)
-
不足 ,不能传输要交换的任何数据
(3)处理完之后,被打断的程序重新执行
6、如何通过管道传递信息?【一个操作的结果作为另一个操作的输入】
- 子进程从父进程继承文件描述符
- file descriptor 0 stdin,1 stdout,2 stderr
- 进程并不关心文件是从哪里里的【键盘、其他文件】
- 整个流程shell是如何处理的呢?
(1)创建管道
(2)为 ls 创建一个进程,设置 **stout **为管道写端
(3)为 **more **创建一个进程,设置为 **stdin **为管道读端
7、如何通过消息队列传递信息?
8、如何通过共享内存传递信息?
- 在进程方面:每个进程都设置了私有地址空间,每个地址空间内,明确地设置了共享内存段
- 优点:快速,方便共享数据
- 不足:必须同步数据访问
- 内存共享具有一些特点:
(1)一个进程写另一个进程立即可见
(2)不需要系统调用干预,不涉及数据复制过程,也不提供同步【程序员提供同步】