利用银行家算法避免死锁

[概述]
操作系统中,银行家算法是避免死锁的一种重要算法。
本文针对《计算机操作系统(第四版)》(汤小丹)p123页的问题:**如果在银行家算法中把P0发出的请求向量改为Request0(0,1,0),系统是否能将资源分配给它,请读者考虑。**进行模拟。
本题有三种资源A,B,C.,5个进程P0~P4。
1.银行家算法中的数据结构
1)可利用资源Available,是一个含有3个元素的数组,分别表示A B C的可利用资源量。
2)最大需求矩阵Max,为一个53的矩阵。
3)分配矩阵Allocation,为一个5
3的矩阵。
4)需求矩阵Need,为一个5*3的矩阵。
其中Need[i,j] = Max[i,j]-Allocation[i,j]
5)结束数组finish,长度为5的bool型数组,用于标记P0~P4这五个进程是否顺序执行完成。
6)out_stack标记,这个标记为bool型,只用于安全性检验中。
有关这个问题的具体描述,请见课本相应位置。
下面,将通过代码模拟这道题的解题过程。

/*
该银行家算法模拟了第122页上的一个例子,从T0时刻模拟。
*/
#include 
#include 
using namespace std;
typedef struct Bank{
    int Available[3] = {2,3,0};  //一共有多少资源
    int Max[5][3] = {7,5,3,
                    3,2,2,
                    9,0,2,
                    2,2,2,
                    4,3,3};//需要多少资源
    int Allocation[5][3] = {0,1,0,
                    3,0,2,
                    3,0,2,
                    2,1,1,
                    0,0,2};    //已分配的资源数
    int Need[5][3];           //还需要多少资源
    bool finish[5] = {false,false,false,false,false};   //完成标记
    bool out_stack = false;     //只用于安全性判断上。为true且在栈顶,出栈。
}Bank;
void Change_Need_Matrix(Bank* bank){
    for(int i=0;i<5;i++){
        for(int j=0;j<3;j++){
            bank->Need[i][j] = bank->Max[i][j] - bank->Allocation[i][j];
        }
    }
}//根据Max和Allocation矩阵求得Need矩阵;Need = Max-Allocation;
Bank* Copy_Bank(Bank* Original_bank){
    Bank* Copy = new Bank();
    for(int i = 0;i<3;i++){
        Copy->Available[i] = Original_bank->Available[i];
    }
    for(int i = 0;i<5;i++){
        for(int j = 0;j<3;j++){
            Copy->Max[i][j] = Original_bank->Max[i][j];
            Copy->Allocation[i][j] = Original_bank->Allocation[i][j];
            Copy->Need[i][j] = Original_bank->Need[i][j];
        }
    }
    for(int i=0;i<5;i++){
        Copy->finish[i] = Original_bank->finish[i];
    }
    Copy->out_stack = Original_bank->out_stack;
    return Copy;
}//返回当前现场bank的拷贝。用于安全性检查中,不能在原题上做安全性检查,这样破坏了现场,所以做了一个拷贝,在这个拷贝上做安全性检查。
bool Judge_Simulation_End(Bank* bank){
    for(int i=0;i<5;i++){
        if(bank->finish[i] == false){
            return false;
        }
    }
    return true;
}    //如果五个进程的finish均为true,就可以结束了。
bool Judge_Process_Finish(int Process_id,Bank* bank){
    for(int i=0;i<3;i++){
        if(bank->Need[Process_id][i] != 0 ){
            return false;
        }
    }
    return true;
}  //判断一个进程是否运行完成,完成了把这个进程的finish置true。
bool Judge_Add_To_Stack(int* request,int* Available){
    for(int i=0;i<3;i++){
        if(request[i]>Available[i]){
            return false;
        }
    }
    return true;
}  //判断要不要加到进程id堆栈和现场堆栈中。
bool Judge_Safe(int id,int* request,Bank* bank){
    /*
    这个函数模拟了一个请求,并判断这个请求是否具有安全性
    id-->进程的id号,int型,0-4
    request-->一个三维int型数组,分别记录进程id记录请求的A,B,C资源的量。
    bank-->原题现场,拷贝的模板
    */
    Bank* temporary_bank = Copy_Bank(bank);   //做一个原现场bank的拷贝temporary_bank。
    for(int i=0;i<3;i++){
        temporary_bank->Need[id][i] -= request[i];
        temporary_bank->Allocation[id][i] += request[i];
        temporary_bank->Available[i] -= request[i];
    }    //在拷贝的现场temporary_bank上执行这个请求。
    if(Judge_Process_Finish(id,temporary_bank) == true){
        temporary_bank->finish[id] = true;
        for(int i=0;i<3;i++){
            temporary_bank->Available[i] += temporary_bank->Allocation[id][i];
        }
    }    //如果执行完这个请求后,id进程结束,则将其Allocation返还给Available。
    if(Judge_Simulation_End(temporary_bank)==true){
        cout<<"资源分配完成!"<<endl;
        exit(0);   //资源分配完成,程序直接退出。
    }    //如果五个进程全都finish,则资源分配完成
    //安全性检查的目的:要找出执行完当前请求后,后面能不能找到一个序列,使各个进程按照这个顺序分配资源执行,全都顺利结束。
    //注意:只要找到一个能成功的分配资源序列,就说明其具有安全性,但其可能有很多分配顺序序列,不必全都找出。
    //找出一个能成功的分配资源序列,需要用到两个栈,一个进程id堆栈,还有一个记录现场的堆栈。两个堆栈同步入栈和出栈。
    stack <int>process_id_stack;    //进程id栈。
    stack <Bank*>scene;             //记录现场的堆栈。这个现场,其实就指当前的temporary_bank。
    for(int i=0;i<5;i++){
        if((temporary_bank->finish[i] == false)&&(Judge_Add_To_Stack(temporary_bank->Need[i],temporary_bank->Available) == true)){
                process_id_stack.push(i);
                scene.push(temporary_bank);
        }
    }//Judge_Add_To_Stack函数根据每个进程的Need和此时刻的Available分别对每个进程判断是否能入栈。
    while(!process_id_stack.empty()){
        if(Judge_Simulation_End(temporary_bank) == true){
            return true;   //模拟过程中,进程全都可结束,其可被安全调度
        }
        else{
            int top = process_id_stack.top();  //取栈顶元素,栈顶元素之所以能入栈,说明其此时可以分配给其全部Need资源。
            Bank* get_bank_stack_top = Copy_Bank(scene.top());
            scene.pop();
            get_bank_stack_top->out_stack = true;
            scene.push(get_bank_stack_top);    //将当前现场栈栈顶的出栈变量修改为true。

            for(int i=0;i<3;i++){
                temporary_bank->Available[i] -= temporary_bank->Need[top][i];
                temporary_bank->Allocation[top][i] += temporary_bank->Need[top][i];
                temporary_bank->Need[top][i] = 0;
                temporary_bank->Available[i] += temporary_bank->Allocation[top][i];
            }     //那就分配给它全部它需要的Need资源喽!
            temporary_bank->finish[top] = true;   //分配完后,Need减为0,这个进程按这种分配方式它已经结束了。
                                                  //但是到这里我们还没有判断这么做是不是满足安全性检查。
            for(int i=0;i<5;i++){
                if((temporary_bank->finish[i] == false)
                    &&(Judge_Add_To_Stack(temporary_bank->Need[i],temporary_bank->Available) == true)){
                    process_id_stack.push(i);
                    temporary_bank->out_stack = false;
                    scene.push(temporary_bank);   //跟上面一样,能入栈的继续入栈。
                }
            }
        }
        if(Judge_Simulation_End(temporary_bank) == true){
            return true;   //模拟过程中,进程全都可结束,其可被安全调度
        }
        while(scene.top()->out_stack == true){
            temporary_bank = scene.top();
            scene.pop();
            temporary_bank->finish[process_id_stack.top()] = false;
            process_id_stack.pop();
        }    //结束一轮时若当前现场栈栈顶出栈标记为true,证明当前模拟过程为不安全的。栈顶元素出栈。
    }
    free(temporary_bank);
    return false;
} //安全性检查函数
bool Judge_Request(int Process_id,int* request,Bank* bank){
    if(bank->finish[Process_id] == true){
        cout<<"进程"<<Process_id<<"已经完成了分配"<<endl;
        return false;
    }   //1.已经完成资源分配的进程不能再进行资源分配。
    int count = 3;
    while(count){
        if(bank->Need[Process_id][count-1]<request[count-1]){
            cout<<"请求资源大于需要资源"<<endl;//2.判断请求是不是小于Need
            return false;
        }
        if(bank->Available[count-1]<request[count-1]){
            cout<<"请求资源大于已有资源"<<endl;//3.判断请求是不是小于Allocation
            return false;
        }
        count--;
    }
    //4.最后进行安全性检查,如果通过安全性检查,就证明其具有安全性,否则不具有安全性。
    return Judge_Safe(Process_id,request,bank);
} //判断请求是否能相应函数。
bool check_diedlock(Bank* bank){
    int count = 0;
    for(int i=0;i<5;i++){
        if(Judge_Add_To_Stack(bank->Need[i],bank->Available) == false){
            count++;
        }
    }
    if(count == 5){
        return true;
    }
    else{
        return false;
    }
}   //检查死锁,如果死锁,操作系统都救不了了。所谓死锁,就是五个进程的Need均大于此时的Available。此时,无论怎么折腾,都是死锁。
void print_table(Bank* bank){
    cout<<"         Allocation              Need                 Available"<<endl;
    cout<<"        A    B    C           A   B   C             A    B    C"<<endl;
    for(int i=0;i<5;i++){
        cout<<"P"<<i<<"      "<<bank->Allocation[i][0]<<"    "<<bank->Allocation[i][1]<<"    "<<bank->Allocation[i][2];
        cout<<"           "<<bank->Need[i][0]<<"   "<<bank->Need[i][1]<<"   "<<bank->Need[i][2];
        if(i == 0){
            cout<<"             "<<bank->Available[0]<<"    "<<bank->Available[1]<<"    "<<bank->Available[2];
        }
        cout<<endl;
    }
}   //打印书P123页的表格到终端。
int main(){
    Bank* bank = new Bank();
    Change_Need_Matrix(bank);
    int id;
    int request_a;
    int request_b;
    int request_c;
    while(Judge_Simulation_End(bank) == false){   //只要进程资源分配没完成,就一直while。
        cout<<"--------------------------------------------------------------------------------"<<endl;
        if(check_diedlock(bank) == true){
            cout<<"这个问题死锁,我也没办法了。"<<endl;
            break;
        }  //先判断一下死锁,如果这个问题本身就是死锁的,也就不用再瞎折腾了。
        /*
        输入一个请求
        */
        cout<<"请输入请求分配资源的进程:";
        cin>>id;
        cout<<"请输入请求分配的A类资源数:";
        cin>>request_a;
        cout<<"请输入请求分配的B类资源数:";
        cin>>request_b;
        cout<<"请输入请求分配的C类资源数:";
        cin>>request_c;
        int request[3] = {request_a,request_b,request_c};
        if(Judge_Request(id,request,bank)==1){
            cout<<"当前的请求安全,可以按照请求分配"<<endl;
            /*
            既然请求安全,就进行资源分配。
            */
            bank->Need[id][0] -= request_a;
            bank->Need[id][1] -= request_b;
            bank->Need[id][2] -= request_c;

            bank->Allocation[id][0] += request_a;
            bank->Allocation[id][1] += request_b;
            bank->Allocation[id][2] += request_c;

            bank->Available[0] -= request_a;
            bank->Available[1] -= request_b;
            bank->Available[2] -= request_c;

            if((bank->Need[id][0] == 0)&&(bank->Need[id][1] == 0)&&(bank->Need[id][2] == 0)){
                bank->Available[0] += bank->Allocation[id][0];
                bank->Available[1] += bank->Allocation[id][1];
                bank->Available[2] += bank->Allocation[id][2];
                bank->finish[id] = true;
            }   //执行完这个资源分配请求,如果有某个进程结束,它当前的finish置为true。
            print_table(bank);  //向用户展示P123页表格,方便下次输入请求。
        }
        else{
            /*
            请求不安全,啥也不做,接着等待用户输入下一个请求。
            */
            cout<<"不安全"<<endl;
            print_table(bank);  //向用户展示P123页表格,方便下次输入请求。
        }
    }
    return 0;
}

[安全性检验算法详解]
写这个代码唯一有收获的地方,就是怎么进行安全性检查。正如注释中所说,只要找到一个资源分配序列满足需求,就可以证明其具有安全性。
笔者采用两个堆栈实现,一个保存进程id号,另一个保存现场,这里的现场指的是temporary_bank。为什么要保存现场?因为在进行安全性测试的时候,其可能会陷入不安全。但此时还不能说明它是死锁的。比如:第一轮分配的时候,P1和P4的Need数组都对应小于此时的Allocation,究竟是选择走P1还是P4,我们不知道。这里有点像剪枝操作。由于P4比P1后入栈,我们先假设走P4这条路,如果走了P4这条路,下一步P0~P4的Need都小于Allocation,则说明P4这条路走不通。此时栈顶元素是P4,他就可以出栈了。出栈后,栈顶元素变成P1,测试P1,P1如果可以的话,就具有安全性。P1要是也不行,P1就也得出栈,此时栈就为空,所有的状态空间搜索完毕,也没有找到一个合适的资源分配序列。那就说明当前请求Request不具有安全性,需要用户重新输入一个具有安全性的Request。
再举个更详细的例子,比如:
当前决定为P1分配资源,就将1入process_id_stack入栈,将为P1分配资源前的bank现场入拷贝的现场栈。此时,现场栈栈顶元素的栈顶入栈标记out_stack为false。然后进入while(!process_id_stack.empty())循环,如果栈空,证明无法找出一个安全的资源分配序列,退出循环,如果栈非空,进入循环。这里栈非空,进入循环,然后temporary_bank为P1分配资源,更新Need,Allocation和Available。然后更新此时拷贝现场栈栈顶P1的出栈标记out_stack为true,然后调用Judge_Add_To_Stack函数判定此时能为哪个进程分配资源。假设此时能为P3,P4分配资源,则P3,P4入栈(注意:本文所说的入栈就是入两个栈,process_id_stack和拷贝的现场栈)。此时栈有底到顶为P1,P3,P4,P1现场栈出栈标记out_stack为true,P3现场栈出栈标记out_stack为false,P4现场栈出栈标记out_stack为false。由于栈顶元素P4为false,不执行栈顶出栈操作,进入下一个while(!process_id_stack.empty())循环,这里栈非空,继续循环,为栈顶进程P4分配资源,并将栈顶现场栈的出栈标记记为true。然后调用Judge_Add_To_Stack函数判定此时能为哪个进程分配资源。假设此时不能为任何进程分配资源,也就是说P1->P4这个分配顺序是不安全的。则没有任何入栈操作。由于栈顶元素P4为true,应该执行出栈操作,此时应该将P4出栈,栈顶指向P3,若P3经过上述类似P4的分析仍不存在一个安全序列,则P3的out_stack标记也会被置为true,在while(!process_id_stack.empty())循环的最后也会被出栈,若P3出栈,此时栈里只剩P1,由于P1现场栈出栈标记也被置为了true,所以P1也出栈。此时栈为空,证明无法找到一个安全的资源分配序列。即当前的资源分配请求是不安全的。这里需要注意的是:栈顶有多少true,就出栈多少。因为这些true对应的结点均为错误资源分配路径上的结点。只有找到false,才可继续判断,false代表沿着当前结点的这条资源分配路径还没有进行判断。
总的来说,这里:
1.每次模拟的时候,先看一下模拟是否结束,如果五个finish都为true,就结束了,就找到了安全序列,就跳出。如果没有,就先为当前栈顶进程分配资源,然后将拷贝现场栈出栈标记out_stack置true。
2.置true后,看当前状态能不能有新进程可以被分配资源,如果有,就将它们入栈,但入栈的时候它们的拷贝现场栈出栈标记仍为false。如果没有,就执行3。
3.栈顶为true,一直出栈。栈顶为false,则跳回到1执行。
下面这个图也是说明了上面这段文字。
利用银行家算法避免死锁_第1张图片

[运行结果]
第一次分配
利用银行家算法避免死锁_第2张图片
把P0发出的请求向量改为Request(0,1,0)。
第二次分配
利用银行家算法避免死锁_第3张图片
P1请求资源分配
第三次分配
利用银行家算法避免死锁_第4张图片
P3请求资源分配
第四次分配
利用银行家算法避免死锁_第5张图片
P0请求资源分配
第五次分配
利用银行家算法避免死锁_第6张图片
P2请求资源分配
第六次分配
利用银行家算法避免死锁_第7张图片
P4请求资源分配
资源分配完成。
这里我们是手动的进行资源分配,也可以用一个算法实现自动的资源分配。

你可能感兴趣的:(算法,操作系统,笔记,算法,操作系统)