十一、死锁与进程间通信
死锁与进程间通信
死锁问题
死锁:如果一个进程集合中的每个进程都在等待只能有该进程集合中的其他进程才能引发的事件,那么,该进程集合就是死锁的(例如,进程1占用资源A,但进程A还在等待资源B,进程2占有资源B,等待占用资源A)
桥梁只能单向通行
-
桥的每个部分可视为一个资源
可能出现死锁:对向行驶车辆在桥上相遇,解决方法:一个方向的车辆倒退(资源抢占和回退)
可能发生饥饿:由于一个方向的持续车流,另一个方向的车辆无法通过桥梁
死锁的系统模型
-
资源类型R1, R2, . . .,Rm:CPU执行时间、内存空间、I/O设备等
-
每类资源Ri有Wi个实例
进程访问资源的流程
请求/获取:申请空闲资源
使用/占用:进程占用资源
释放:资源状态由占用变成空闲
资源分类
可重用资源(ReusableResource)
-
资源不能被删除且在任何时刻只能有一个进程使用
-
进程释放资源后,其他进程可重用
-
可重用资源示例
硬件:处理器、I / O通道、主和副存储器、设备等
软件:文件、数据库和信号量等数据结构
可能出现死锁:每个进程占用一部分资源并请求其它资源
消耗资源(Consumableresource)
- 资源创建和销毁
消耗资源示例:在I/O缓冲区的中断、信号、消息等
-
可能出现死锁:进程间相互等待接收对方的消息
资源分配图:描述资源和进程间的分配和占用关系的有向图
两类顶点
-
系统中的所有进程:P ={P1, P2, …, Pn}
-
系统中的所有资源:R ={R1, R2, …, Rm}
两类有向边
-
资源请求边:进程Pi请求资源Rj:Pi指向Rj
-
资源分配边:资源Rj已分配给进程Pi:Rj 指向 Pi
总结:
如果图中不包含循环,则没有死锁
如果图中包含循环
- 如果每个资源只有一个实例,那么死锁
- 如果每个资源类有几个实例,可能死锁
死锁的特征
死锁可能出现如果四个条件同时成立:死锁的必要条件
互斥:任何时刻只能有一个进程使用一个资源实例
持有并等待:进程保持至少一个资源,并正在等待获取其他进程持有的资源
非抢占:一个资源只能在进程使用后自愿释放
循环等待:存在等待进程集合{P0,P1,...,PN} ,P0正在等待P1所占用的资源,P1 正在等待P2占用的资源,...,PN-1在等待PN所占用资源,PN正在等待P0所占用的资源
死锁的处理办法
死锁预防(DeadlockPrevention):确保系统永远不会进入死锁状态
死锁避免(DeadlockAvoidance):在使用前进行判断,只允许不会出现死锁的进程请求资源
死锁检测和恢复(DeadlockDetection & Recovery):在检测到运行系统进入死锁状态后,进行恢复
由应用进程处理死锁,通常操作系统忽略死锁(鸵鸟算法),因为判断死锁的开销较大,大多数操作系统(包括UNIX)的做法。
死锁的预防:限制申请方式
预防是采用某种策略,限制并发进程对资源的请求,使系统在任何时刻都不满足死锁的必要条件。
互斥:把互斥的共享资源封装成可同时访问,会造成不确定性
持有并等待:进程请求资源时,要求它不持有任何其他资源,仅允许进程在开始执行时,一次请求所有需要的资源。资源利用率低,一开始占用所有资源,占用时间过长,导致其他进程无法执行,发生饥饿。
非抢占:
- 如果进程占用某些资源,并请求其他不能立即分配的资源,则释放当前已占有资源
- 被抢占的资源添加到资源列表中
- 只有当它能够获得旧的资源以及它请求新的资源,进程可以得到执行。
循环等待:对资源类型进行排序,要求进程按顺序请求资源。(在嵌入式OS中较多)
死锁避免
死锁避免是利用额外的先验信息,在分配资源时判断是否会出现死锁,只在不会死锁时分配资源
- 最简单有效就是要求每个进程声明它需要的每个类型资源的最大数目
- 资源的分配状态是通过限定提供与分配的资源数目,和进程的最大需求
-
死锁避免算法要动态检查资源的分配状态,以确保永远不会有一个环形等待状态
系统资源分配的安全状态
当一个进程请求可用资源时,系统判断分配后是否处于安全状态
系统处于安全状态是指:针对所有已占用进程,存在安全序列
序列<P1,P2,...,PN>是安全的:针对每个Pi,Pi要求的资源≤当前可用资源+所有Pj 持有资源,其中j<i。
如果Pi的资源请求不能立即分配,则Pi等待所有Pj(j<i)完成
Pi完成后,Pi+1可得到所需资源,执行,释放所分配的资源并终止
同样方法,最终整个序列的所有Pi都能获得所需资源
安全状态与死锁的关系
-
系统处于安全状态,一定没有死锁
系统处于不安全状态,可能出现死锁(某些进程可以释放资源让其他进程先运行,避免死锁):避免死锁就是确保系统不会进入不安全状态
死锁避免算法:银行家算法(Banker'sAlgorithm)
银行家算法是一个避免死锁产生的算法。以银行借贷分配策略为基础,判断并保证系统处于安全状态
客户在第一次申请贷款时,声明所需最大资金量,在满足所有贷款要求并完成项目时,及时归还
在客户贷款数量不超过银行拥有的最大值时,银行家尽量满足客户需要
类比:银行家——操作系统,资金——资源,客户——申请资源的线程
数据结构
安全状态判断
如果在最后第2步中,即有线程为false,但是need>work,则出现不安全的状态。
算法:
银行家算法的安全状态判断示例
T4也能够完成,因此这是一个安全状态,则分配。
银行家算法的安全状态判断示例2
不安全状态。
死锁检测和死锁修复
死锁加检测继续放宽了条件,允许系统进入死锁状态,利用死锁检测算法检测。出现死锁时,用死锁恢复机制进行恢复
死锁检测
方法一:判断资源等待图是否又环方法二:在资源分配图里面判断
数据结构
算法
开销很大,对于银行家需要提前知道每个进程的最大资源数,信息很难获得,在实际OS中几乎没有用到。
死锁检测示例
死锁检测示例二
死锁检测算法的使用
何时、使用什么样的频率来检测依赖于
- 死锁多久可能会发生
- 多少进程需要回滚
如果检测算法多次被调用,有可能资源图有多个循环,所以我们无法分辨出多个可能死锁的进程中的哪些“造成”死锁
死锁恢复
1.死锁恢复进程终止:(存在强制性和不合理性)
终止所有的死锁进程
一次只终止一个进程直到死锁消除
终止进程的顺序应该是
进程的优先级(优先级低kill)
进程已运行时间以及还需运行时间
进程已占用资源
进程完成需要的资源
多少进程需要被终止
进程是交互还是批处理
2.死锁恢复:资源抢占(也不是很理想)
选择被抢占进程:最小成本目标
进程回退:返回到一些安全状态, 重启进程到安全状态
可能出现饥饿:同一进程可能一直被选作被抢占者
进程通信(IPC, Inter-Process Communication)
概述
进程通信是进程进行通信和同步的机制。保证进程间相对独立性的同时(一个进程不能随便访问另一个进程的地址空间)的,确保进程正确运行) ,保证进行有效的沟通(进程与进程需要协作完成大的任务,需要通知与数据传递)。
IPC提供2个基本操作
发送操作:send(message)
-
接收操作:receive(message)
进程通信流程:
-
在通信进程间建立通信链路
-
通过send/receive交换消息
进程链路特征
-
物理 (如,共享内存,硬件总线)
-
逻辑 (如,逻辑属性)
通信方式:
直接通信:
进程必须正确的命名对方
- send (P, message) – 发送信息到进程P
- receive(Q,message) – 从进程 Q接受消息
通信链路的属性
自动建立链路(OS的支持)
-
一条链路恰好对应一对通信进程
每对进程之间只有一个链接存在
链接可以是单向的,但通常为双向的
间接通信:
通过操作系统维护的消息队列实现进程间的消息接收和发送
-
每个消息队列都有一个唯一的标识
-
只有共享了相同消息队列的进程,才能够通信
通信链路的属性
-
只有共享了相同消息队列的进程,才建立链路
-
消息队列可以与多个进程相关联
每对进程可以共享多个消息队列
连接可以是单向或双向
通信流程
-
创建一个新的消息队列
-
通过消息队列发送和接收消息
销毁消息队列
基本通信操作
send(A,message) – 发送消息到队列A
-
receive(A,message) – 从队列 A接受消息
阻塞与非阻塞通信:
消息传递可以是为阻塞(同步)或非阻塞(异步)
阻塞通信
-
阻塞发送:发送者在发送消息后进入等待,直到接收者成功收到
-
阻塞接收:在请求接收消息后进入等待,直到成功收到一个消息
非阻塞通信
-
非阻塞发送:发送者在消息发送后,可立即进行其他操作
-
非阻塞接收:没有消息发送时,接收者在请求接收消息后,接收不到任何消息
通信链路缓冲
进程发送的消息在链路上可能有3种缓冲方式
-
0 容量:发送方必须等待接收方
-
有限容量:通信链路缓冲队列满时,发送方必须等待,同理缓冲为空时,接收方要等待
-
无限容量:发送方不需要等待,但接收方有可能要等待(无数据)。
信号(Signal)
信号(与之前提到的同步互斥中的信号量不同)
-
进程间的软件中断通知和处理机制
-
如:SIGKILL,SIGSTOP, SIGCONT等
信号的接收处理
-
捕获(catch):执行进程指定的信号处理函数被调用
-
忽略(Ignore):执行操作系统指定的缺省处理,例如:进程终止、进程挂起等
屏蔽(Mask):禁止进程接收和处理信号,可能是暂时的(当处理同样类型的信号)
不足:传送的信息量小,只有一个信号类型,起到通知作用。不能传输要交换的任何数据
信号的实现
信号使用示例
管道(pipe)
进程间基于内存文件的通信机制
-
子进程从父进程继承文件描述符
-
缺省文件描述符:0 stdin, 1 stdout,2 stderr
进程不知道(或不关心!)的另一端
- 可能从键盘、文件、程序读取
- 可能写入到终端、文件、程序
管道示例
与管道相关的系统调用
-
读管道:read(fd,buffer, nbytes),scanf()是基于它实现的
-
写管道:write(fd,buffer, nbytes),printf()是基于它实现的
-
创建管道:pipe(rgfd)
rgfd是2个文件描述符组成的数组
rgfd[0]是读文件描述符,rgfd[1]是写文件描述符
消息队列
管道通过父进程帮子进程建立好通道,要有父子关系。而且管道数据是字节流,没有结构化变现形式。消息队列可以克服上面的缺陷。
消息队列是由操作系统维护的以字节序列为基本单位的间接通信机制
每个消息(Message)是一个字节序列
-
相同标识的消息组成按先进先出顺序组成一个消息队列(MessageQueues)
多个不相干的可以通过消息队列传递数据,可以没有父子关系,sender可以是结构化数据,用管道一样,它也有buffer size的限制
消息队列的系统调用
共享内存
直接通信方式
共享内存是把同一个物理内存区域同时映射到多个进程的内存地址空间的通信机制
进程
每个进程都有私有内存地址空间
-
每个进程的内存地址空间需明确设置共享内存段
线程
同一进程中的线程总是共享相同的内存地址空间
优点:快速、方便地共享数据
不足:必须用额外的同步机制来协调数据访问
共享内存的实现:把同一个物理内存区域同时映射到多个进程的内存地址空间
共享内存系统调用