实验四 模拟银行家算法

前言

  • 参考教材《操作系统 第四版》汤小丹
  • 最近发生了些不愉快,计划寒假系统更新一些知识(还没想好,计划是就业方向类的,可以推荐推荐哇)!感觉内容质量不高,仅供参考,只放全部代码,这次就不分开讲了

实验目的

了解掌握银行家算法,学会模拟实现资源分配,同时有要求编写和调试一个系统分配资源的简单模拟程序,观察死锁产生的条件,并使用

适当的算法,有效的防止和避免死锁的发生

实验内容

1.实验背景

在多道程序系统中,虽可借助与多个进程的并发执行,来改善系统的资源利用率,提高系统的吞吐量,但可能发生一种危险—死琐。

产生死锁的原因可归结为两点:

1)竞争资源。当系统中供多个进程共享的资源如打印机、公用队列等,其数目不足以满足诸进程的需要时,会引起诸进程对资源的竞争而产生死锁。

2)进程间推进顺序非法。进程在运行过程中,请求和释放资源的顺序不当,也同样会导致产生进程死锁。

最有代表性的避免死锁的算法,是Dijkstra的银行家算法。这是由于该算法能用与银行系统现金贷款的发放而得名的

什么是银行家算法?
  银行家算法(Banker’s Algorithm)是一个避免死锁(Deadlock)的著名算法,是由艾兹格·迪杰斯特拉在1965年为T.H.E系统设计的一种避免死锁产生的算法。它以银行借贷系统的分配策略为基础,判断并保证系统的安全运行。
  在银行中,客户申请贷款的数量是有限的,每个客户在第一次申请贷款时要声明完成该项目所需的最大资金量,在满足所有贷款要求时,客户应及时归还。银行家在客户申请的贷款数量不超过自己拥有的最大值时,都应尽量满足客户的需要。在这样的描述中,银行家就好比操作系统,资金就是资源,客户就相当于要申请资源的进程。
  银行家算法是一种最有代表性的避免死锁的算法。在避免死锁方法中允许进程动态地申请资源,但系统在进行资源分配之前,应先计算此次分配资源的安全性,若分配不会导致系统进入不安全状态,则分配,否则等待。为实现银行家算法,系统必须设置若干数据结构

2.算法描述

设Request[i] 是进程Pi的请求向量,如果Requesti[j]=K,表示进程Pi需要K个Rj类型的资源,当Pi发出资源请求后,系统按下面步骤进行检查:

(1)如果Requesti[j]<=Need[i,j],便转向步骤2;否则认为出错,因为它所需要的资源数已超过它所宣布的最大值。

(2)如果Requesti[j]<=Available[j],便转向步骤3;否则,表示尚无足够资源,Pi须等待。

(3)系统试探着把资源分配给进程Pi,并修改下面数据结构中的数值:

Available[j]:=Available[j]-Requesti[j];

Allocation[i,j]:=Allocation[i,j]+Requesti[j];

Need[i,j]:=Need[i,j]-Requesti[j];

(4)系统执行安全性算法,检查此次资源分配后,系统是否处于安全状态。若安全,才正式将资源分配给进程Pi,以完成本次分配;否则,将本次的试探分配作废,恢复原来的资源分配状态,让进程Pi等待

3.数据结构

银行家算法中的数据结构:

(1) 可利用资源向量Available。这是一个含有n个元素的数组,其中的每一个元素代表一类可利用的资源数目,其初始值是系统中所配置的该类全部可用资源的数目,其数值随该类资源的分配和回收而动态地改变。如果Available[j]=K,则表示系统中现有Rj类资源K个

(2)最大需求矩阵Max。这是一个m*n的矩阵,它定义了系统中n个进程中每一个进程对m类资源的最大需求。如果Max[i,j]=K,则表示进程i需要Rj类资源的最大数目为K

(3)分配矩阵Allocation。这也是一个m*n的矩阵,它定义了系统中每一类资源当前已分配给每一进程的资源数。如果Allocation[i,j]=K,则表示进程i当前已分得Rj类资源的数目为K

(4)需求矩阵Need1。这也是一个n*m的矩阵,用以表示每一个进程尚需的各类资源数。如果Need[i,j]=K,则表示进程i还需要Rj类资源K个,方能完成其任务

(5) 工作数组Work。这是一个含有n个元素的数组,它代表可以提供分配的资源数,初始值是Available中的数值,随着资源的回收,它的值也会改变,公式是Work[i]=Work[i]+Allocation[i]

实验步骤

流程图

image-20211214205632904

完整代码

package 操作系统实验;

import java.util.Arrays;
import java.util.Scanner;
/*
* 银行家算法
* @author 张时贰
* @data 2021年12月14日
*/
public class 银行家算法 {
// 首先定义全局变量
static int resourceNum = 3; // 初始资源种类数
static int processNum = 5; // 初始进程数
static int[] available = new int[] { 3, 3, 2 }; // 可用(剩余)资源j的数量
static int[][] maxRequest = new int[][] { { 7, 5, 3 }, { 3, 2, 2 }, { 9, 0, 2 }, { 2, 2, 2 }, { 4, 3, 3 } }; // 进程i对资源j的最大需求量
static int[][] allocation = new int[][] { { 0, 1, 0 }, { 2, 0, 0 }, { 3, 0, 2 }, { 2, 1, 1 }, { 0, 0, 2 } }; // 进程i已分配资源j的数量
static int[][] need = new int[][] { { 7, 4, 3 }, { 1, 2, 2 }, { 6, 0, 0 }, { 0, 1, 1 }, { 4, 3, 1 } }; // 进程i还需资源j的数量
static boolean[] finish = new boolean[] { false, false, false, false, false }; // 是否安全
static int[] safeSeries = new int[] { 0, 0, 0, 0, 0 }; // 安全序列号
static int[] request = new int[] { 0, 0, 0 }; // 进程请求资源j的数量
static int num; // 资源数量计数
Scanner sc = new Scanner(System.in);

// 打印:初始资源、进程信息
public static void showInfo() {
System.out.println("**************************************************************");
System.out.println("当前系统各类资源剩余:");
System.out.println(Arrays.toString(available));

System.out.printf("\n当前系统资源情况:\n");
System.out.printf(" PID\t\t maxRequest\tAllocation\t Need\n");

for (int i = 0; i < processNum; i++) {
System.out.printf(" P%d\t\t", i);
for (int j = 0; j < resourceNum; j++) {
System.out.printf("%2d", maxRequest[i][j]);
}
System.out.printf("\t\t");
for (int j = 0; j < resourceNum; j++) {
System.out.printf("%2d", allocation[i][j]);
}
System.out.printf("\t\t");
for (int j = 0; j < resourceNum; j++) {
System.out.printf("%2d", need[i][j]);
}
System.out.println();
}
}

/**
* 打印安全检查信息
*
* @param work 工作向量,表示系统可提供给进程继续运行所需的各类资源数目
* @param i 第i个进程
*/
public static void safeInfo(int[] work, int i) {
int j;
System.out.printf(" P%d\t\t", i);
for (j = 0; j < resourceNum; j++) {
System.out.printf("%2d", work[j]);
}
System.out.printf("\t\t");
for (j = 0; j < resourceNum; j++) {
System.out.printf("%2d", allocation[i][j]);
}
System.out.printf("\t\t");
for (j = 0; j < resourceNum; j++) {
System.out.printf("%2d", need[i][j]);
}
System.out.printf("\t\t");
for (j = 0; j < resourceNum; j++) {
System.out.printf("%2d", allocation[i][j] + work[j]);
}
System.out.println();
}

/**
* 判断一个进程的资源数是否全为0,全为0,则说明已执行完毕,可释放其持有的资源
*
* @param i 第i个进程
* @return
*/
static boolean isAllZero(int i) {
num = 0;
for (int j = 0; j < resourceNum; j++) {
if (need[i][j] == 0) { // 进程i已完成
num++;
}
}
if (num == resourceNum) {
return true;
} else {
return false;
}
}

/**
* 安全性检查
*
* @return
*/
static boolean Safe() {
int safeIndex = 0;
int allFinishNum = 0;
// 工作向量,表示系统可提供给进程继续运行所需的各类资源数目
int[] work = new int[resourceNum];
int r = 0; // 第r个进程
int temp = 0;
int pNum = 0;

for (int j = 0; j < resourceNum; j++) {
work[j] = available[j];
}

// 标识进程完成(true)或未完成(false)
for (int i = 0; i < processNum; i++) {
if (isAllZero(i)) {
finish[i] = true;
allFinishNum++;
} else {
finish[i] = false;
}
}

// 预分配开始
while (allFinishNum != processNum) { // 进程并未全部完成
num = 0; // 进程r,所需资源种类数
for (int j = 0; j < resourceNum; j++) {
// 当某进程未完成,且其资源需求量小于当前系统中该资源的剩余量时
if (need[r][j] <= work[j] && finish[r] == false) {
num++;
}
}
if (num == resourceNum) { // 当num等于resourceNum时,说明 进程r 的三种资源情况都满足上述if条件,即都可以分配成功
allFinishNum++;
safeInfo(work, r);
safeSeries[safeIndex] = r;
safeIndex++;
finish[r] = true;
for (int j = 0; j < resourceNum; j++) { // 进程执行完毕,释放 进程r 所持有的资源
work[j] = work[j] + allocation[r][j];
}
}
r++;
if (r >= processNum) {
r = r % processNum;
if (temp == allFinishNum) {
break;
}
temp = allFinishNum;
}
pNum = allFinishNum;
}

// 判断系统是否安全
for (int i = 0; i < processNum; i++) {
if (finish[i] == false) {
System.out.printf("\n当前系统不安全!\n\n");
return false;
}
}

// 打印安全序列
System.out.printf("\n当前系统安全~\n\n安全序列为:");
for (int i = 0; i < processNum; i++) {
if (isAllZero(i)) {
pNum--;
}
}
for (int i = 0; i < pNum; i++) {
System.out.printf("%d ", safeSeries[i]);
}
return true;
}

public static void main(String[] args) {
int curProcess = 0;
showInfo();
System.out.printf("\n\n系统安全情况分析:\n");
System.out.printf(" PID\t\t Work\t\t Allocation\t Need\t\t Work+Allocation\n");
boolean safe = Safe();
Scanner scanner = new Scanner(System.in);
while (safe) {
// 用户输入或预设系统资源分配合理才继续进行进程分配工作
do {
if (curProcess >= processNum || curProcess < 0) { // 注:进程标识序列从0开始
System.out.printf("\n请不要输入超出进程数量的值或其他字符:\n");
}
System.out.printf("\n**************************************************************\n");
System.out.printf("\n输入要分配的进程:");
curProcess = scanner.nextInt(); // 接收用户输入的进程号
System.out.println();
} while (curProcess >= processNum || curProcess < 0);

// 用户输入或预设系统资源分配合理才继续进行进程分配工作
for (int j = 0; j < resourceNum; j++) {
int requestJ = 0;
do {
if (requestJ < 0 || requestJ > available[j]) {
System.out.printf("\n请不要输入超出进程所需资源数量或输入为负数或其他字符:\n");
}
System.out.printf("请输入要分配给进程 P%d 的第 %d 类资源:", curProcess, j + 1);
requestJ = scanner.nextInt();
request[j] = requestJ;
} while (requestJ < 0 || requestJ > available[j]);
}

// 判断用户输入的分配是否合理,合理则进行预分配
num = 0;
for (int j = 0; j < resourceNum; j++) {
if (request[j] <= need[curProcess][j] && request[j] <= available[j]) {
num++;
} else {
System.out.printf("\n发生错误!可能原因如下:\n(1)您请求分配的资源可能大于该进程的某些资源的最大需要!\n(2)系统所剩的资源已经不足了!\n");
break;
}
}
if (num == resourceNum) {
num = 0;
for (int j = 0; j < resourceNum; j++) {
// 更新资源信息
available[j] = available[j] - request[j];
allocation[curProcess][j] = allocation[curProcess][j] + request[j];
need[curProcess][j] = need[curProcess][j] - request[j];
if (need[curProcess][j] == 0) { // 此进程的资源j已满足所需
num++;
}
}

// 如果分配以后,该进程对所有所需资源需求为0,那么视为完成,释放此进程
if (num == resourceNum) {
for (int j = 0; j < resourceNum; j++) {
available[j] = available[j] + allocation[curProcess][j];
}
System.out.printf("\n\n本次分配进程 P%d 完成,该进程占用资源全部释放完毕!\n", curProcess);
} else {
System.out.printf("\n\n本次分配进程 P%d 未完成!\n", curProcess);
}

showInfo();
System.out.printf("\n系统安全情况分析\n");
System.out.printf(" PID\t\t Work\t\tAllocation\t Need\t\tWork+Allocation\n");

// 预分配完成以后,判断该系统是否安全,若安全,则可继续进行分配,若不安全,将已经分配的资源换回来
if (!Safe()) {
for (int j = 0; j < resourceNum; j++) {
available[j] = available[j] + request[j];
allocation[curProcess][j] = allocation[curProcess][j] - request[j];
need[curProcess][j] = need[curProcess][j] + request[j];
}
System.out.printf("资源不足,等待中...\n\n分配失败!\n");
}
}
}
}
}

运行结果

image-20211214205827356

image-20211214205843315

image-20211214205854450

image-20211214205924825

image-20211214210035309

image-20211214205907366

总结

 银行家算法是一种最有代表性的避免死锁的算法。在避免死锁方法中允许进程动态地申请资源,但系统在进行资源分配之前,应先计算此次分配资源的安全性,若分配不会导致系统进入不安全状态,则分配,否则等待。为实现银行家算法,系统必须设置若干数据结构