操作系统死锁

一、死锁的定义

死锁是指多个进程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。

二、死锁产生原因

(1) 竞争资源引起死锁

可剥夺资源——CPU、主存
非剥夺资源——磁带机、打印机

永久性资源——可顺序重复使用的资源   如:打印机等
临时性资源——可以动态生成和消耗 如:硬件中断、消息等

只有对不可剥夺资源的竞争才可能产生死锁

(2)进程推进顺序非法

进程在运行过程中,请求和释放资源的顺序不当,也同样会导致死锁。例如,并发进程P1,P2分别保持了资源R1,R2,而进程P1申请资源R2、进程P2申请资源R1时,两者都会因为所需资源被占用而阻塞,于是导致死锁。

信号量使用不当也会造成死锁。进程间彼此相互等待对方发来的消息,也会使得这些进程间无法继续向前推进。例如,进程A等待进程B发的消息,进程B又在等待进程A发的消息,可以看出进程A和B不是因为竞争同一资源,而是在等待对方的资源导致死锁。

三、死锁产生必要条件

(1)互斥条件

进程要求对所分配的资源(如打印机)进行排他性使用,即在一段时间内某资源仅为一个进所占有。此时若有其他进程请求该资源,则请求进程只能等待。

(2)不剥夺条件

进程所获得的资源在未使用完之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放(只能是主动释放)。

(3)请求并保持条件

进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。

(4)循环等待条件

存在一种进程资源的循环等待链,链中每个进程已获得的资源同时被链中下一个进程所请求。

四、处理死锁的基本方法

1.预防死锁:破坏四个必要条件中的一个或几个条件。易于实现,但会导致资源利用率和系统吞吐量降低
2.避免死锁:用某种方法在资源动态分配过程中,防止系统进入不安全状态。实现有难度,但可获得较高的资源利用率和系统吞吐量。
3.检测与解除死锁:允许死锁发生。通过检测机构检测,然后采取措施,清除死锁,检测配套完成。常通过撤销或挂起一些进程实现。

下面分成三个部分分别介绍处理死锁的方法

Part1:死锁预防

1. 破坏互斥条件

若允许系统资源都能共享使用,则系统不会进入死锁状态。但有些资源根本不能同时访问,如打印机等临界资源只能互斥使用。所以,破坏互斥条件而预防死锁的方法不太可行,而且在有的场合应该保护这种互斥性。

2. 破坏不剥夺条件

当一个已保持了某些不可剥夺资源的进程请求新的资源而得不到满足时,它必须释放已经保持的所有资源,待以后需要时再重新申请。这意味着,一个进程已占有的资源会被暂时释放,或者说是被剥夺,或从而破坏了不剥夺条件。

该策略实现起来比较复杂,释放已获得的资源可能造成前一阶段工作的失效,反复地申请释放资源会增加系统开销,降低系统吞吐量。这种方法常用于状态易于保存和恢复的资源,如CPU的寄存器及内存资源,一般不能用于打印机之类的资源。

3.破坏请求并保持条件

采用预先静态分配方法,即进程在运行前一次申请完它所需要的全部资源,在它的资源未满足前,不把它投入运行。一旦投入运行,这些资源就一直归它所有,不再提出其他资源请求,这可以保证系统不会发生死锁。(改进方法:分阶段申请资源)

这种方式实现简单,但缺点也显而易见,系统资源被严重浪费,其中有些资源可能仅在运行初期或运行快结束时才使用,甚至根本不使用。而且还会导致“饥饿”现象,由于个别资源长期被其他进程占用时,将致使等待该资源的进程迟迟不能开始运行。

4. 破坏循环等待条件

为了破坏循环等待条件,可采用顺序资源分配法。首先给系统中的资源编号,规定每个进程必须按编号递增的顺序请求资源,同类资源一次申请完。也就是说,只要进程提出申请分配资源R,则该进程在以后的资源申请中就只能申请编号大于Ri的资源。

这种方法存在的问题是,编号必须相对稳定,这就限制了新类型设备的增加;尽管在为资源编号时已考虑到大多数作业实际使用这些资源的顺序,但也经常会发生作业使用资源的顺序与系统规定顺序不同的情况,造成资源的浪费;此外,这种按规定次序申请资源的方法,也必然会给用户的编程带来麻烦。

Part2:死锁避免

避免死锁同样属于事先预防策略,但并不是事先采取某种限制措施破坏死锁的必要条件,而是在资源动态分配过程中,防止系统进入不安全状态,以避免发生死锁。这种方法所施加的限制条件较弱,可以获得较好的系统性能。

1.如果系统能够按照某种顺序为每个进程分配其所需资源,直至最大需求,则当前状态为安全状态;否则成为不完全状态、换言之,如系统找不到一个安全序列,则系统处于不安全状态。
2.进程推进的顺序称为安全序列。

银行家算法是最著名的死锁避免算法

银行家算法

进程运行之前先声明对各种资源的最大需求量,当进是在执行中继续申请资源时,先测试该进程已占用的资源数与本次中请的资源数之和是否超过该进程声明的最大需求量。 若超过则拒绝分配资源,若未超过则再测试系统现存的资源能否满足该进程尚需的最大资源量,若能满足则按当前的申请量分配资源,否则也要推迟分配。

(1)数据结构描述

可利用资源向量Available;含有m个元素的数组,其中每个元素代表一类可用的资源数目。Awalable[j]=K表示系统中现有Rj类资源K个。

最大需求矩阵Max: n x m矩阵,定义系统中n个进程中的每个进程对m类资源的最大需求。简单来说,一行代表一个进程,一列代表一类资源。Max[i , j]=K表示进程i需要Rj类资源的最大数目为K。

分配矩阵 Allocation: n x m矩阵,定义系统中每类资源当已分配分每个进程的资源数。

Allocation[i , j] = K表示进程i当前已分得Rj类资源的数目为K。

需求矩阵Need: n x m矩阵,表示每个进程接下来最多还需要多少资源。Need[i , j]=K表示进程i还需要Rj类资源的数目为K。

上述三个矩阵间存在下述关系:

Need =Max- Allocation

算法描述:

假设用Requesti[j]=k表示并发执行时进程i提出请求j类资源k个,系统按下述步骤进行检查:
 (1)如果Requesti ≤ Needi则继续以下检查,否则显示需求申请超出最大需求值的错误。
 (2)如果Requesti ≤ Available则继续以下检查,否则显示系统无足够资源,Pi阻塞等待。
 (3)系统试探性的把要求的资源分配给进程i并修改有关数据结构的值:
   Available=Available-Request;
   Allocation=Allocation+Request;
   Need=Needi-Request;
(4)系统执行安全性算法,检查此次资源分配后,系统是否处于安全状态,若安全,才正式将资源分配给进程i,以完成本次分配;否则将试探分配作废,恢复原来的资源分配状态,让进程Pi等待。

(2)安全性算法

安全性算法:假定系统同意这次资源申请后,可否找到一个安全序列

实现过程:

1.初始化设置工作向量Work[m]表示系统可提供的各类资源数目,用以保护原数据结构有关值。Work = Available;设置完成标志向量 Finish[n]。Finish[i] = false 表示进程i尚未完成,如值为true则表示进程i已完成(可以顺利推进)。
2.从进程集合n中找到一个能满足下述二个条件:
 Finish[i] = false和Need[i]≤Work(主要是找这个)

如找到则执行步骤3,如找不到同时满足以上二条件的进程则执行步骤4。
3.当进程i获得资源后可顺利执行直到完成,并释放出分配给它的资源,表示如下:
 work = work+Allocation; Finish[i]=true ;转执行步骤B。
4.如果所有的Finish[i]=true,则表示系统处于安全状态,否则系统处于不安全状态。

算法描述很啰嗦不如来一道例题实在

Example:

假定系统中有五个进程{P0、P1、P2、P3、P4}和三种类型资源{A、B、C},每一种资源的数量分别为10、5、7。各进程的最大需求、T0时刻资源分配情况如下所示。

1.T0时刻是否安全?
2.P1请求资源Request1(1,0,2)是否允许?
3.P4请求资源Request4(3,3,0)是否允许?
4.P0请求资源Request(0,2,0)是否允许?

第一问:检测T0时刻是否安全就用到了安全性算法

先把need和allocation抄下来,接着对比work和need,work>need且false时,加入序列,然后系统可分配资源work+=allocation,并置true

安全序列可以有很多种,这里只是写了其中一种

第二问到第四问相同解法

以下为代码实现(非多线程),对非法输入也没做处理

对于OP操作

1.检查该时刻是否安全

2.请求资源是否允许

例题的输入

7 5 3 0 1 0 7 4 3
3 2 2 2 0 0 1 2 2
9 0 2 3 0 2 6 0 0
2 2 2 2 1 1 0 1 1
4 3 3 0 0 2 4 3 1
3 3 2
1
2
1 1 0 2
2
4 3 3 0
2
0 0 2 0

#include<bits/stdc++.h>
using namespace std;

class triple{
public:
    int A;
    int B;
    int C;
};

const int N = 5;
triple Max[N],Allocation[N],Need[N];
triple Available;

//安全性算法
bool check(bool ifprint)
{
    vector<string>result;
    bool Finish[N];
    memset(Finish,0,sizeof Finish);
    triple Work;
    Work.A = Available.A;
    Work.B = Available.B;
    Work.C = Available.C;
    while(1){
        bool isfind = false;
        for(int i=0;i<N;i++){
            int A,B,C;
            A = Need[i].A,B = Need[i].B,C = Need[i].C;
            if(A<=Work.A && B<=Work.B && C<=Work.C){
                if(!Finish[i]){
                    isfind = true;
                    Finish[i] = true;
                    Work.A += Allocation[i].A;
                    Work.B += Allocation[i].B;
                    Work.C += Allocation[i].C;
                    result.push_back("P"+to_string(i));
                    break;
                }
            } 
        }
        int cnt = 0;
        for(int i=0;i<N;i++){
            if(Finish[i]) cnt++;
        }
        if(cnt==N){
            if(!ifprint) return true;
            cout<<"该时刻安全"<<endl<<"安全序列为:";
            for(auto it:result) cout<<it<<" ";
            cout<<endl;
            return true;
        }

        if(!isfind){
            if(!ifprint) return false;
            cout<<"该时刻不安全"<<endl;
            return false;
        }

    }
}

//银行家算法
bool banker(int idx,int a,int b,int c)
{  
    if(a>Need[idx].A ||b>Need[idx].B||c>Need[idx].C ) return false;
    if(a>Available.A ||b>Available.B||c>Available.C) return false;
    //假设分配资源
    Available.A-=a,Available.B-=b,Available.C-=c;
    Allocation[idx].A+=a,Allocation[idx].B+=b,Allocation[idx].C+=c;
    Need[idx].A-=a,Need[idx].B-=b,Need[idx].C-=c;
    if(check(false)) return true;
    else{
        //假设不成立,回溯
        Available.A+=a,Available.B+=b,Available.C+=c;
        Allocation[idx].A-=a,Allocation[idx].B-=b,Allocation[idx].C-=c;
        Need[idx].A+=a,Need[idx].B+=b,Need[idx].C+=c;
        return false;
    }
}

int main()
{
    //freopen("testin.txt","r",stdin);
    cout<<"从P0~P4依次输入Max向量 Allocation向量 Need向量"<<endl;
    for(int i=0;i<5;i++){
        cin>>Max[i].A>>Max[i].B>>Max[i].C;
        cin>>Allocation[i].A>>Allocation[i].B>>Allocation[i].C;
        cin>>Need[i].A>>Need[i].B>>Need[i].C;
    }
    cout<<"输入Available向量"<<endl;
    cin>>Available.A>>Available.B>>Available.C;


    int op;
    while(cin>>op){
        // cout<<"1.检查该时刻是否安全"<<endl;
        // cout<<"2.请求资源是否允许"<<endl;
        if(op==1){
            cout<<"检查当前时刻是否安全..."<<endl;
            check(true);
        }else if(op==2){
            int idx,a,b,c;
            cin>>idx>>a>>b>>c;
            cout<<"进程"<<idx<<"请求资源"<<"("<<a<<","<<b<<","<<c<<")"<<endl;
            if(banker(idx,a,b,c)) cout<<"请求合法"<<endl;
            else cout<<"请求不合法"<<endl;
        }
    }

    return 0;
}

Part3:死锁检测和解除

死锁的检测

资源分配图(resource allocation graph)用于描述系统的死锁

1.是一个有向图G;
2.结点:为资源或进程,
3.边: 从资源R到进程P的边表示R已分配给P,从进程P到资源R的边表示P正因请求R而处于等待状态。
4.有向图的循环是死锁存在的必要条件

 有环有死锁

 有环无死锁

 死锁定理

当且仅当S状态的资源分配图是不可完全简化的,称S为死锁状态——该充分条件称为死锁定理。其中的有边进程就是死锁进程。

资源分配图的简化

操作系统------资源分配图化简_你好,明天,,的博客-CSDN博客

死锁的解除

1.重新启动
2.进程回退:回滚每个死锁进程到前一个检查点,重新执行每个进程。
3.撤销死锁进程
    (1)全部撤销;
    (2)按照某种原则逐个选择死锁进程进行撤消,直到解除系统死锁

选择原则:一般选择系统付出代价最小的进程,即:花费处理机的时间最少、输出最少、估计未执行部分最多、已分配的资源量最少、优先级最低
4.剥夺资源
    按照某种原则逐个剥夺进程资源,直到解除死锁。

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值