OS之进程管理 --- 死锁
什么是死锁
在正常操作模式下,进程按如下顺序来使用资源:
- 申请:进程请求资源
- 使用:进程对资源进行操作
- 释放:进程释放资源
当一组进程中的每一个进程度在等待一个事件,而这事件只能有一组进程的另一个进程引起,那么这组进程就处于死锁状态。
死锁的特征
我们来看一个例子:互斥锁的死锁
采用互斥锁的多线程Pthreads程序可能发生死锁。函数pthread_mutex_init()将一个互斥锁初始化为未加锁。函数pthread_mutex_lock()和pthread_mutex_unlock()分别获得和释放互斥锁,当一个线程试图获得一个已加锁的互斥锁时,它会堵塞,知道该互斥锁的所有者调用pthread_mutex_unlock()
创建两个互斥锁:
pthread_mutex_t first_mutex;
pthread_mutex_t second_mutex;
pthread_mutex_init(&first_mutex, NULL);
pthread_mutex_init(&second_mutex, NULL);
创建两个线程,即thread_one和thread_two,这些线程都能访问这两个互斥锁,thread_one和thread_two分别运行在do_work_one()和do_work_two()中:
void *do_work_one(void *param) {
pthread_mutex_lock(&first_mutex);
pthread_mutex_lock(&second_mutex);
pthread_mutex_unlock(&second_mutex);
pthread_mutex_unlock(&first_mutex);
pthread_exit(0);
}
void *do_work_two(void *param) {
pthread_mutex_lock(&second_mutex);
pthread_mutex_lock(&first_mutex);
pthread_mutex_unlock(&first_mutex);
pthread_mutex_unlock(&second_mutex);
pthread_exit(0);
}
这个例子中因为两个线程获取和释放互斥锁的顺序不同,有可能造成死锁。注意:即使有可能死锁,但是不一定会发生。
必要条件
如果一个系统中下面四个条件同时成立,那么就会引起死锁:
- 互斥:至少有一个资源必须处于非共享模式,即一次只有一个进程可以使用。
- 占有并等待:一个进程应占有至少一个资源,并等待另一个资源,而该资源为其他进程所占有。
- 非抢占:资源不能被抢占,即资源只能被进程完成任务后自愿释放。
- 循环等待:有一组进程 {P0P_0P0,P1P_1P1,…,PnP_nPn},P0P_0P0等待的资源为P1P_1P1占有,P1P_1P1等待的资源为P2P_2P2所占有,…,Pn−1P_{n-1}Pn−1等待的资源被PnP_nPn所占有。
强调:只有这四个条件同时成立时才会出现死锁。
资源分配图
资源分配图是一种有向图,该图包括一个节点集合V和一个边集合E。节点集合V可以分为两种类型:P={P1P_1P1,P2P_2P2,…,PnP_nPn}(系统中所有活动进程的集合)和R={R1R_1R1,R2R_2R2,…,RmR_mRm}(系统中所有资源类型的集合).
从进程PiP_iPi到资源类型RjR_jRj的有向边记为PiP_iPi→\rightarrow→RjR_jRj,他表示进程PiP_iPi已经申请了资源类型RjR_jRj的一个实例,并且正在等待这个资源;RjR_jRj→\rightarrow→PiP_iPi表示资源的一个实例已经分配给进程PiP_iPi了。有向边PiP_iPi→\rightarrow→RjR_jRj称为申请边,RjR_jRj→\rightarrow→PiP_iPi称为分配边。
具体的资源分配图详解:资源分配图
根据资源分配图的定义,可以证明:如果分配图没有环,那么系统没有进程死锁。如果分配图中存在环,那么可能存在死锁。
如果每个资源类型刚好有一个实例,那么有环就意味着已经出现死锁。如果环上每个类型只有一个实例,那么就出现了死锁。换上的进程就死锁。在这种情况下图中的环就是死锁存在的充分且必要条件。
如果每个资源类型有多个实例,那么有环并不意味这已经出现了死锁,在这种情况下,图中的环就是死锁存在的必要不充分条件。
死锁的处理方法
一般来说,处理死锁的问题有三种:
- 通过协议来预防或避免死锁,确保系统不会进入死锁状态。
- 可以允许系统进入死锁状态,然后检测并加以恢复
- 可以忽视这个问题,认为死锁不可能在系统内发生。
第三种方法被大多数操作系统所采用,包括Linux和Windows。
死锁预防
互斥
互斥条件必须成立,也就是说,至少一个资源应该是非共享的。实际当中,操作系统中一定需要互斥资源,所有通过解决互斥来打破死锁的四个条件是不合理的。
持有且等待
当每个进程申请一个资源的时候,它不能占有其他资源。
一种可以采用的协议:每个进程在执行前申请并获得所有的资源。即要求进程申请资源的系统调用在其他系统调用之前完成。
另外一种协议:允许进程仅在没有资源时才可申请资源。一个进程可申请一些资源并使用他们,在申请更多的其他资源之前,它应该释放现在已经分配的资源。
两者的差异:第一种要求在进程执行前申请执行所需的所有的资源,在执行过程中不能够在申请,执行完成后释放所占有的所有资源;第二种要求先分配给进程当前可以执行的资源数量,进程执行过程中如果缺少资源进请求,在请求时先释放自己占有的资源。
缺点:
- 资源利用率比较低
因为许多资源坑你已经分配,但是很长时间没有使用 - 可能发生饥饿
一个进程如需要多个常用资源,可能必须永久等待,因为在它所需要的资源中至少有一个已经分配给其他进程。
无抢占
打破“不能抢占已分配的资源”,为了保证这个条件不成立,采用协议:如果一个进程持有资源并申请另外一个不能立即分配的资源(也就是说,这个进程应该等待),那么他现在分配的资源都可被抢占。可以理解为这些资源都被隐式的释放了。被抢占资源添加到进程等待的资源列表中。只有当进程获得其原有资源和申请的新资源时,他才可以重新执行。
循环等待
确保循环等待条件不成立的一个方法是:对所有资源类型进行完全排序,而且要求每个进程按递增顺序来申请资源。
假设资源类型的集合是R={R1R_1R1, R2R_2R2,…,RmR_mRm},为每个资源类型分配一个唯一整数,这样可以比较两个资源以确定他们的先后顺序。
我们采用如下协议:每个进程只能按递增顺序申请资源。即一个进程开始可申请任何数量的资源类型RiR_iRi的实例。换句话说,要求当一个进程申请资源类型RjR_jRj时,他应该先释放所有资源RiR_iRi(F(RjR_jRj) ≤\leq≤ F(RiR_iRi))。如果需要同一类型的多个实例,那么应该一起申请。
死锁避免
死锁避免算法动态的检查资源分配状态,以便确保遵化你等待条件不能成立。
安全状态
如果系统能够按一定顺序来为每个进程分配资源,仍然避免死锁,那么系统的状态就是安全的。更正式的说,只有存在一个安全序列,系统才处于安全状态。
进程序列<P1P_1P1, P2P_2P2, … , PnP_nPn>在当前分配状态下为安全序列是指:对于每个PiP_iPi,PiP_iPi仍然可以申请资源数小于当前可用资源加上所有进程PjP_jPj(j < i)所占有的资源。在这种情况下,进程PiP_iPi需要的资源即使不能立即可用,那么PiP_iPi可以等待直到所有PjP_jPj释放资源。当他们完成后,PiP_iPi可得到需要的所有资源完成给定任务,返回分配的资源,最后终止。当PiP_iPi终止时,Pi+1P_{i+1}Pi+1可得到他需要的资源,如此进行,如果没有这样的序列存在,那么系统状态就是非安全的。
安全状态不是死锁状态,相反,死锁状态是非安全状态。然而不是所有的非安全状态都能导致死锁状态。非安全状态可能导致死锁。只有在安全状态下,操作系统就能避免非安全(和死锁)状态。在非安全状态下,操作系统不能阻止进程申请资源,因而可能死锁。进程行为控制了非安全状态。
资源分配图算法
除了申请边和分配边,引入需求边,需求边PiP_iPi→\rightarrow→RjR_jRj表示进程PiP_iPi可能在将来某个时候申请资源RjR_jRj。当进程PiP_iPi申请资源时RjR_jRj时,需求边PiP_iPi→\rightarrow→RjR_jRj变成了申请边,类似当进程PiP_iPi释放RjR_jRj时,分配边RjR_jRj→\rightarrow→PiP_iPi变成了需求边PiP_iPi→\rightarrow→RjR_jRj。
现在假设进程PiP_iPi申请资源RjR_jRj,只有在将申请边PiP_iPi→\rightarrow→RjR_jRj变成分配边RjR_jRj→\rightarrow→PiP_iPi并且不会导致资源分配图形成环时,才能允许申请。
如果没有环存在,那么资源的分配会使系统处于安全状态。如果有环存在,那么分配会导致系统处于非安全状态,这种情况下,进程PiP_iPi应该等待资源申请。
银行家算法
设n为系统进程的数量,m为资源类型的种类。定义数据结构:
Available:长度为m的向量,表示每种资源的可用实例数量,如果Available[j]=k,那么资源类型RjR_jRj有k个可用实例。
Max:n×\times×m矩阵,定义每个进程的最大需求。如果Max[i][j]=k,那么进程PiP_iPi最多可申请源类型RjR_jRj的k个实例
Allocation:n×\times×m矩阵,定义每个进程现在分配的每种资源类型的实例数量,如果Allocation[i][j]=k,那么进程PiP_iPi现在已分配了资源类型RjR_jRj的k个实例。
Need:n×\times×m矩阵,表示每个进程还需要的剩余资源。如果Need[i][j]=k,那么进程PiP_iPi还可能申请k个资源类型RjR_jRj的实例。
注意:Need[i][j]=Max[i][j] - Allocation[i][j]。
安全算法
通过安全算法以求出系统是否处于安全状态,描述如下:
- 令Work和Finish分别为长度m和n的向量,对于i=0,1,…,n-1,初始化Work=Available和Finish[i]=false。
- 查找这样的i使其满足:
a. Finish[i] == false
b. NeediNeed_iNeedi ≤\leq≤Work
如果没有这样的i存在,那么转到第4步 - Work=Work+AllocationiAllocation_iAllocationi
Finish[i]=true
返回第2步 - 如果对所有i,Finish[i]=true,那么系统处于安全状态。
这个算法可能需要m ×\times× n2n^2n2数量级的操作,以确定系统状态是否安全。
资源请求算法
现在描述是否安全允许请求的算法:
设RequestiRequest_iRequesti为进程PiP_iPi的请求向量。如果RequestiRequest_iRequesti[j]==k,那么进程PiP_iPi需要的资源类型RjR_jRj的实例数量为k,当进程PiP_iPi做出这一资源请求时,采取如下动作:
- 如果RequestiRequest_iRequesti ≤\leq≤ NeediNeed_iNeedi,转到第二步。否则生成出错条件,进程PiP_iPi已超过了其最大需求。
- 如果RequestiRequest_iRequesti ≤\leq≤ Available,转到第三步,否则PiP_iPi应等待,这是因为没有资源可用。
- 嘉定系统可以分配给PiP_iPi请求的资源,并案如下方式修改状态:
Available = Available - RequestiRequest_iRequesti
AllocationiAllocation_iAllocationi = AllocationiAllocation_iAllocationi + RequestiRequest_iRequesti
NeediNeed_iNeedi = NeediNeed_iNeedi - RequestiRequest_iRequesti
如果新的资源分配状态是安全的,那么交易完成且进程PiP_iPi可分配到需要的资源。然而,如果新状态不安全,那么进程PiP_iPi应等待RequestiRequest_iRequesti并恢复到原来的资源分配状态。
死锁检测
如果一个系统既不采用死锁预防算法也不采用死锁避免算法,那么死锁可能出现,在这种情况下,系统可以提供:
- 一个用来检查系统状态从而确定是否出现死锁的算法
- 一个用来从死锁状态恢复的算法
每个资源类型只有单个实例
如果所有资源类型只有单个实例,那么可以通过等待图来作为死锁检测算法。
等待图就是从资源分配图中,删除所有资源类型节点,合并适当边,从而得到等待图
当且仅当在等待图中有一个环,系统死锁。为了检测死锁,系统需要维护等待图,并周期调用用于搜索图中环的算法。
注意:等待图算法不适用于每种资源类型可有多个实例的资源分配系统
每种资源类型可有多个实例
定义如下数据结构:
Available:长度为m的向量,表示各种资源的可用实例数量
Allocation:n×\times×m矩阵,表示每个进程的每种资源的当前分配数量
Request:n×\times×m矩阵,表示当前每个进程的每种资源的当前请求,如果Request[i][j]=k,那么PiP_iPi现在正在请求资源类型RjR_jRj的k个实例。
可以看出该算法和银行家算法类似,可以进行比较理解,该算法的描述如下:
- 令Work和Finish分别为长度m和n的向量,初始化Work=Available。对于i=0,1,…,n-1,如果AllocationiAllocation_iAllocationi不为0,则Finish[i]=false,否则Finish[i]=true。
- 找到这样的i,同时满足:
a. Finish[i]=false
b. RequestiRequest_iRequesti ≤\leq≤ Work
如果没有这样的i,则转到第4步 - Work = Work + AllocationiAllocation_iAllocationi
Finish[i] = true
转到第2步 - 如果对某个i(0 ≤\leq≤ i ≤\leq≤ n),Finish[i] == false,则系统死锁,并且如果Finish[i] == false,则进程PiP_iPi死锁。
死锁恢复
进程终止
通过终止进程来消除死锁,有两种方法,都是通过允许系统回收终止进程的所有分配资源:
- 终止所有死锁进程
这种方法代价很大。这些死锁进程可能已经计算了较长时间,这些部分计算的结果也要放弃,并且以后可能还要重新计算 - 一次终止一个进程,知道消除死锁循环为止
这种方法的开销相当大,因为每次终止一个进程,都要调用死锁检测算法,以确定是否仍有进程处于死锁。
资源抢占
通过资源抢占来消除死锁,我们不断抢占一些进程的资源以便给其他进程使用,直到死锁循环被打破为止。如果要采用抢占来处理死锁,需要考虑三个问题:
- 选择牺牲进程
- 回滚
被抢占的进程不能继续正常执行,我们应将该进程回滚到某个安全状态,以便从该状态重启进程。 - 饥饿
即如何保证资源不会总是从同一进程中被抢占
参见:《操作系统概念》(第九版)
OS之进程管理 --- 死锁的更多相关文章
- Python::OS 模块 -- 进程管理
os模块的简介参看 Python::OS 模块 -- 简介 os模块的文件相关操作参看 Python::OS 模块 -- 文件和目录操作 os模块的进程参数 Python::OS 模块 -- 进程参数 ...
- OS之进程管理---多线程模型和线程库(POSIX PTread)
多线程简介 线程是CPU使用的基本单元,包括线程ID,程序计数器.寄存器组.各自的堆栈等,在相同线程组中,所有线程共享进程代码段,数据段和其他系统资源. 传统的的单线程模式是每一个进程只能单个控制线程 ...
- OS之进程管理---进程调度和多线程调度
进程调度基本概念 多道程序的目标就是始终允许某个进程运行以最大化CPU利用率,多个进程通时存在于内存中,操作系统通过进程调度程序按特定的调度算法来调度就绪队列中的进程到CPU,从而最大限度的利用CPU ...
- OS之进程管理---实时CPU调度
引言 一般来说,我们将实时操作系统区分为软实时系统(soft real-time system)和硬实时系统(hard real-time system).软实时系统不保证会调度关键实时进程,而只保证 ...
- OS之进程管理---多处理器调度
引言 之前我们所学习的操作系统进程调度策略的前提条件是单处理器系统下的CPU调度程序.如果系统中存在多个CPU,那么负载分配就成为可能,但是相应的调度问题就会更加复杂. 多处理器调度方法 对于多处理器 ...
- OS之进程管理---孤儿进程和僵尸进程
僵尸进程 当一个进程终止时,操作系统会释放其资源,不过它位于进程表中的条目还是在的,直到它的父进程调用wait():这是因为进程表中包含了进程的退出状态.当进程已经终止,但是其父进尚未调用wait() ...
- Python::OS 模块 -- 进程参数
os模块的简介请参看 Python::OS 模块 -- 简介 os模块的文件和目录操作 Python::OS 模块 -- 文件和目录操作 os模块的进程管理 Python::OS 模块 -- 进程管理 ...
- python 标准类库-并行执行之subprocess-子进程管理
标准类库-并行执行之subprocess-子进程管理 by:授客QQ:1033553122 1.使用subprocess模块 以下函数是调用子进程的推荐方法,所有使用场景它们都能处理.也可用Popen ...
- boost:进程管理
概述 Boost.Process提供了一个灵活的C++ 进程管理框架.它允许C++ developer可以像Java和.Net程序developer那样管理进程.它还提供了管理当前执行进程上下文.创建 ...
随机推荐
- java中配置自定义拦截器中exclude-mapping path是什么意思?
<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/>//过滤全部请求 & ...
- 着重基础之—Java 8 Comparator: How to Sort a List (List排序)
着重基础之—Java 8 Comparator: How to Sort a List (List排序) 首先申明,这篇博客的内容不是我自己的知识,我是从国外网站搬来的,原因有二:1是因为大天朝对网络 ...
- git图解:代码区域总结
本文背景,在实际项目中使用git已有一年多,发现不少同事虽然会使用常用git指令,但并不理解每个指令对应的作用原理.今天静下心总结下git 的基本理解:代码的存在区域:本文以实际项目出发,理清使用gi ...
- C#-安全
分为两种,代码访问安全,基于角色的安全性. 代码访问安全.是代码告诉.net框架,自己(代码)正确执行,需要的权限,.net框架手动分配代码可执行操作方面的权限,代码可列出调用自己需要的权限集合. 基 ...
- (二叉树)Elven Postman -- HDU -- 54444(2015 ACM/ICPC Asia Regional Changchun Online)
http://acm.hdu.edu.cn/showproblem.php?pid=5444 Elven Postman Time Limit: 1500/1000 MS (Java/Others) ...
- Spark-1.2.2部署
1.安装Scala 1.1解压和安装 在Scala官网http://www.scala-lang.org/download/下载Scala安装包,然后解压.(注:JDK的版本最好是1.7及以上,否则S ...
- hdu 5685 Problem A (逆元)
题目 题意:H(s)=∏i≤len(s)i=1(Si−28) (mod 9973),求一个字符串 子串(a 位到 b 位的)的哈希值.这个公式便是求字符串哈希值的公式,(字符的哈希值 = 字符的ASC ...
- Swagger中显示注释
Webapi中Swagger中不显示注解的解决方法 1.找见生成项目时候生成的xml文件.查看存放路劲方法: 右键项目-->点击属性-->在属性中选择“生成”就能看见xml文件存放路径: ...
- RabbitMQ基础入门篇
下载安装 Erlang RabbitMQ 启动RabbitMQ管理平台插件 DOS下进入到安装目录\sbin,执行以下命令 rabbitmq-plugins enable rabbitmq_manag ...
- Cesium简介 [转]
http://www.cnblogs.com/laixiangran/p/4984522.html 一.Cesium介绍 Cesium是国外一个基于JavaScript编写的使用WebGL的地图引擎. ...