简介操作系统银行家算法实验报告与源代码面向操作系统课程学习者与对死锁避免机制感兴趣的开发人员用于理解并实现由迪杰斯特拉提出的资源分配安全策略。实验报告以docx文档系统讲解算法的四个核心数据结构最大需求矩阵、可用资源向量、已分配矩阵、需求矩阵及安全性检查流程从初始化、资源请求到安全序列判定、分配与释放均有说明。源码由BankAlgorithm.h、BankAlgorithm.cpp与main.cpp三个C文件组成覆盖进程管理、资源分配和模拟循环附带initialize.txt可快速构造测试用例。整个压缩包共5个文件约452KB轻量便于下载阅读。已有5248人学习下载适合正在完成操作系统实验、课程设计或复试备考的读者参考既能对照文档逐段验证算法逻辑也能基于现有代码扩展多资源类型与多进程模拟场景。1. 银行家算法实验为什么操作系统课总拿死锁避免来卡你临近操作系统期末实验室里最常出现的一幕是一群人围着一台电脑看一个 C 语言程序在屏幕上反复打印同一组数字却看不到任何推进。银行家算法Bankers Algorithm作为操作系统课程里死锁避免的必修实验要求你同时交一份能跑的源代码和一份像样的实验报告。它干的事其实很直接在进程申请资源时先模拟分配一次再判断整个系统是否仍然存在安全序列安全才放行不安全就回滚拒绝。这个“先试后批”的流程恰好把死锁避免与死锁检测的区别暴露得清清楚楚。这篇文章直接对着实验报告和源代码两个交付物来拆从数据结构、安全检测、请求处理到测试用例和常见翻车点每一步都写到你能够照着复现。2. 安全状态判定与数据结构设计先把“安全”两个字说透2.1 安全状态与安全序列为什么系统安全就不会死锁银行家算法的思想来自 Dijkstra讲的是银行家给多个企业放贷不能等所有企业同时来提款时才发现现金不够。操作系统把资源看成现金每个进程在创建时就声明自己对每类资源的最大需求量 Max并且保证运行结束后会把已经占用的资源全部释放。系统手里的现金就是 Available 向量。关键问题变成当一个进程发出资源请求时能不能把钱借给它而不会把自己逼到谁都借不到钱的绝境。这里要先立住两个概念。安全状态指的是存在一个进程序列 P1, P2, …, Pk使得对序列中任意一个进程 Pi它还需要的资源量可以通过“当前可用资源 排在它前面的所有进程释放的资源”来满足。这个序列就叫安全序列。注意安全序列不一定唯一只要存在至少一条系统就是安全的。不安全状态则是不存在任何安全序列但它不意味着当前已经死锁只是说如果所有进程都继续申请资源将来很可能谁也走不动。为什么“安全”比“不死锁”更强因为死锁检测是事后补救等死锁发生了再找出是谁占着资源不放而银行家算法在每一次分配前做预防宁可不满足这个请求也不让系统进入可能死锁的区域。这就是操作系统课程里常说的“死锁避免”与“死锁预防”和“死锁检测与解除”并列为三大处理策略。实验报告的需求分析部分第一个要写清楚的就是你选的是避免策略而不是检测策略这两者在题目里经常被混着问。2.2 四个核心数据结构Available、Max、Allocation、Need 的关系写代码之前先把实验里的四个核心数据结构列成一张表这张表建议直接抄进实验报告的设计部分。数据结构类型含义对应实验报告里的描述Available一维数组每类资源当前可用的数量系统资源余量Max二维矩阵每个进程对每类资源的最大需求进程资源需求上限Allocation二维矩阵每个进程当前已经分到的每类资源数量进程资源占有情况Need二维矩阵每个进程还缺的每类资源数量进程剩余需求它们之间的关系是 Need Max – Allocation。这个式子看起来简单代码里却极其容易出问题。初始化时你可以让用户直接输入 Need也可以让用户输入 Max 和 Allocation 再由程序相减。我一般建议后者因为手工计算 Need 很容易算错而且老师在验收时通常会问“你这三个矩阵是怎么保持一致性的”你回答说程序自动算的比说“我手算填进去的”更有说服力。每一次成功的资源分配Available、Allocation、Need 三个东西要同时更新Available 减去请求量Allocation 加上请求量Need 减去请求量。少更新任何一个后续的安全检测结果都会失真。还有一个容易被忽略的点资源种类数不止一个所以这四个结构都要能表示多类资源Available 是长度为 m 的数组另外三个是 n×m 的矩阵。2.3 C语言里的存储与初始化矩阵、向量和输入校验实验里最常用的语言是 C因为操作系统课程普遍要求能用 gcc 编译的命令行程序。定义方式可以直接用固定大小的数组实验规模一般不会超过 10 个进程、10 类资源没必要引入动态内存。#include stdio.h #include stdbool.h #define MAX_PROCESS 10 // 最大进程数 #define MAX_RESOURCE 10 // 最大资源种类数 int process_num; // 实际进程数 int resource_num; // 实际资源种类数 int available[MAX_RESOURCE]; int max_need[MAX_PROCESS][MAX_RESOURCE]; int allocation[MAX_PROCESS][MAX_RESOURCE]; int need[MAX_PROCESS][MAX_RESOURCE]; void init_data(void) { printf(输入进程数和资源种类数); scanf(%d %d, process_num, resource_num); printf(输入各类资源的 Available 向量\n); for (int j 0; j resource_num; j) { scanf(%d, available[j]); } printf(输入 Max 矩阵\n); for (int i 0; i process_num; i) { for (int j 0; j resource_num; j) { scanf(%d, max_need[i][j]); } } printf(输入 Allocation 矩阵\n); for (int i 0; i process_num; i) { for (int j 0; j resource_num; j) { scanf(%d, allocation[i][j]); if (allocation[i][j] max_need[i][j]) { printf(错误进程%d已分配资源超过最大需求\n, i); return; } } } for (int i 0; i process_num; i) { for (int j 0; j resource_num; j) { need[i][j] max_need[i][j] - allocation[i][j]; } } printf(初始化完成\n); }这段代码的逻辑分三段先读进程数和资源种类数再读 Max 矩阵和 Allocation 矩阵最后用减法计算 Need。上面在输入 Allocation 时顺手做了合法性校验凡是已分配量大于最大需求的输入都属于数据错误直接终止。参数说明MAX_PROCESS 和 MAX_RESOURCE 是宏决定了数组上界如果你要跑更大的用例改宏比改代码里的循环边界要安全得多available 数组下标 j 对应第 j 类资源max_need[i][j] 里的 i 对应第 i 个进程j 对应资源种类行列别写反这是后面所有循环的前提。3. 从安全检测到资源请求C语言实现银行家算法的最小源代码3.1 安全性检测寻找安全序列的经典写法安全检测是整个算法的心脏它要回答一个问题在当前 Available、Allocation、Need 的状态下系统是否存在至少一条安全序列。实现时我把工作向量 Work 和完成标记 Finish 都做成函数局部变量避免在检测过程中污染全局数组。bool is_safe(int safe_sequence[]) { int work[MAX_RESOURCE]; bool finish[MAX_PROCESS] { false }; int seq_len 0; for (int j 0; j resource_num; j) { work[j] available[j]; } // 最坏情况下每轮只能推进一个进程所以最多循环 process_num 轮 for (int round 0; round process_num; round) { bool found false; for (int i 0; i process_num; i) { if (finish[i]) continue; // 检查进程 i 的剩余需求是否都不大于当前可用资源 bool can_run true; for (int j 0; j resource_num; j) { if (need[i][j] work[j]) { can_run false; break; } } if (can_run) { // 假设进程 i 运行完释放它占用的资源 for (int j 0; j resource_num; j) { work[j] allocation[i][j]; } finish[i] true; safe_sequence[seq_len] i; found true; } } if (!found) break; // 这一轮没有找到任何可推进的进程直接结束 } for (int i 0; i process_num; i) { if (!finish[i]) return false; } return true; }逻辑说明外层循环最多执行 process_num 轮因为 n 个进程最多需要 n 次释放动作内层每次找一个“剩余需求全部不大于 work”的未完成进程找到后把它的 allocation 加进 work并标记完成。found 变量起到剪枝作用如果一整轮下来没有任何进程能被推进那剩余进程永远无法推进可以直接判定不安全。参数说明safe_sequence 是输出型参数调用方传入一个 int 数组函数会把找到的安全序列按顺序填进去。这里故意让 work 和 finish 都是局部变量函数返回后全局状态不变这一点是安全检测能被反复调用的关键。如果你的安全检测函数直接修改了全局 available那第二个请求进来时系统状态就已经错了。3.2 资源请求处理试分配、安全检测、回滚请求处理是银行家算法里最容易翻车的地方。它必须严格按“先检查合法性再检查可用性然后试分配最后安全检测失败就回滚”的顺序写。顺序不对程序的行为就会变得很“玄学”。bool request_resource(int pid, int request[]) { // 第一步检查请求量是否超过进程声明的最大需求 for (int j 0; j resource_num; j) { if (request[j] need[pid][j]) { printf(进程%d请求超过剩余需求拒绝\n, pid); return false; } } // 第二步检查可用资源是否足够 for (int j 0; j resource_num; j) { if (request[j] available[j]) { printf(进程%d请求资源不足进入等待\n, pid); return false; } } // 第三步试分配临时修改全局状态 for (int j 0; j resource_num; j) { available[j] - request[j]; allocation[pid][j] request[j]; need[pid][j] - request[j]; } // 第四步安全检测 int seq[MAX_PROCESS]; if (is_safe(seq)) { printf(安全序列为); for (int i 0; i process_num; i) { printf(P%d , seq[i]); } printf(\n); return true; } else { // 第五步回滚把状态还原成试分配之前 for (int j 0; j resource_num; j) { available[j] request[j]; allocation[pid][j] - request[j]; need[pid][j] request[j]; } printf(进程%d请求导致系统进入不安全状态已回滚并拒绝\n, pid); return false; } }逻辑说明前三步好理解关键是第五步的回滚。一旦 is_safe 返回 falseallocation、available、need 三个数组必须全部还原少一个就会让下一次判断基于错误状态。我见过不少同学只回滚 available结果后续进程的请求结果越来越离谱半天查不出原因。参数说明pid 是进程编号进程编号从 0 开始实验报告里的 P0、P1 和数组下标保持一致request[] 长度与 resource_num 一致由主循环读入。这里用一个 int seq[MAX_PROCESS] 来接收安全序列如果你不关心安全序列具体长什么样也可以直接传 NULL但传 NULL 时 is_safe 内部不能写 safe_sequence所以实际写代码时建议始终传一个数组。3.3 主循环与交互菜单让演示样例能跑起来主循环不需要华丽能把多组请求串起来演示就够了。每个请求前后都打印一次当前状态表格这份输出截图就是实验报告“运行结果与分析”部分的最佳素材。void print_state(void) { printf(Available: ); for (int j 0; j resource_num; j) printf(%d , available[j]); printf(\n); printf(进程 Max Allocation Need\n); for (int i 0; i process_num; i) { printf(P%d , i); for (int j 0; j resource_num; j) printf(%d , max_need[i][j]); printf( ); for (int j 0; j resource_num; j) printf(%d , allocation[i][j]); printf( ); for (int j 0; j resource_num; j) printf(%d , need[i][j]); printf(\n); } } int main(void) { init_data(); int choice; while (1) { printf(\n1. 请求资源 2. 打印当前状态 0. 退出\n); scanf(%d, choice); if (choice 0) break; if (choice 2) { print_state(); continue; } int pid, req[MAX_RESOURCE]; printf(输入进程编号); scanf(%d, pid); printf(输入 %d 类资源的请求量, resource_num); for (int j 0; j resource_num; j) { scanf(%d, req[j]); } request_resource(pid, req); } return 0; }这段代码没什么花哨的功能但它保证了你在 linux 操作系统终端里执行gcc banker.c -o banker ./banker就能跑起来。print_state 打印的内容建议直接照抄进实验报告表头对齐以后可以手动微调不必在代码里追求完美对齐。参数说明就一点choice 为 1 时读入 pid 和请求向量进程编号越界的情况你们可以自己加一层判断我在实验里一般会加避免老师测试时随手输入一个 99 把程序搞崩。4. 实验报告怎么组织从需求分析到测试用例的四个关键部分4.1 报告的骨架与篇幅分配实验报告写不好源代码再漂亮也容易丢分。银行家算法实验报告的通用结构分四块每一块都有明确的写作目标。报告章节需要写的内容建议篇幅常见空洞写法需求分析问题背景、输入输出格式、算法目标1 页左右整段抄教材定义算法设计四个数据结构、安全检测流程图、请求处理流程2 页左右只有流程图没有文字核心代码is_safe 与 request_resource 的关键实现1 页左右全代码粘贴没有解释测试与分析至少 3 个用例预期结果与实际输出对比1 页左右只贴截图不写分析需求分析里要写清楚输入格式也就是 Available、Max、Allocation 的读入顺序以及进程数和资源种类数的范围。算法设计部分除了数据结构表还要有流程图这里不需要画多漂亮用 Visio 或 draw.io 画“开始 → 输入初始化 → 接收请求 → 检查请求合法性 → 试分配 → 安全检测 → 是正式分配 / 否回滚拒绝”这种线性流程就够了。4.2 测试用例设计安全、不安全、边界请求各一个测试用例是报告里最加分也最容易露怯的部分。只跑一个教材上的安全例子完全不够至少要三个场景安全分配、不安全分配、非法请求。经典的五进程三资源例子必须会手动演算。Available (3, 3, 2)Max 矩阵和 Allocation 矩阵如下进程MaxAllocationNeedP07 5 30 1 07 4 3P13 2 22 0 01 2 2P29 0 23 0 26 0 0P32 2 22 1 10 1 1P44 3 30 0 24 3 1手动找安全序列先看谁还需要的每一类资源都不大于 (3,3,2)。P1 的 Need 是 (1,2,2)满足假设 P1 运行完释放 Allocation (2,0,0)Work 变成 (5,3,2)。再看剩下进程P3 的 Need 是 (0,1,1)也满足推进 P3 后 Work 变成 (7,4,3)。接着 P0 的 Need(7,4,3) 恰好吃满P2 和 P4 也能被满足安全序列 P1 → P3 → P0 → P2 → P4 成立。不安全例子可以这样构造把 Available 调小比如让 Available (1,1,1)其余数据不变此时每个进程的 Need 都无法满足程序输出“找不到安全序列”。边界请求的例子是让某个进程请求 0 个资源正确输出是分配成功且安全序列不变或者让请求量大于 Need程序必须拒绝并提示超限。4.3 报告里容易被问倒的几个追问验收时老师很少只让你跑一遍演示通常会追问三个问题。第一个为什么银行家算法是保守的因为每个进程提前声明了 Max且系统假设进程最终会释放全部资源但现实中的进程不一定能预知自己需要多少资源。第二个安全状态是否一定不死锁安全状态保证存在执行序列只要其他进程不提出离谱请求就不会死锁但一个实际发生的请求仍可能让系统进入不安全状态所以是“避免”而不是“保证”。第三个算法的时间复杂度是多少安全检测是 O(n² × m)n 是进程数m 是资源种类数这也是它适合教学而不适合现代操作系统直接全量使用的原因之一。5. 银行家算法实现中的常见问题与排错试分配翻车现场5.1 安全检测函数污染了全局状态现象第一次调用 is_safe 之后再发起第二次请求明明可用资源没变判断结果却跟着变甚至打印出的 Available 变成了负数。原因is_safe 内部直接对全局 available 做了 work[j] allocation[i][j] 这样的操作没有用局部变量拷贝安全检测变成了“修改式检测”。解决像 3.1 节那样把 work 和 finish 全部声明为函数局部变量is_safe 只读全局数组不写任何全局数据。这是排在第一位的坑因为它的表现很隐蔽查起来又费时间。5.2 外层循环轮数不够导致安全序列判不出来现象明明手动演算存在安全序列程序却输出“不安全”或者打印出的安全序列少了后半段进程。原因安全检测的外层循环如果只写for (int i 0; i process_num; i)且循环体里没有“这轮没找到就 break”的保护程序可能在某些中间状态下提前退出。还有一种写法是把外层 for 写在进程下标 i 上却把同一轮找到多个可推进进程也算成了多个轮次逻辑混乱。解决外层循环与进程下标解耦写成for (int round 0; round process_num; round)每轮至少推进一个进程找到就置 found true找不到就 break。这样逻辑和教材伪代码完全一致。5.3 试分配后只回滚了一半数组现象一次请求被拒绝后打印状态发现 allocation 没有还原need 和 available 却还原了表格数据互相矛盾。原因request_resource 的回滚分支漏写了 allocation[pid][j] - request[j]或者顺序写反先改了 available 再改 need结果中间某一步抛错。解决回滚代码必须与试分配代码严格对称。我写时会故意把试分配和回滚放在相邻两段每段都按 available、allocation、need 的固定顺序写三条语句并用注释隔开这样检查起来一目了然。5.4 scanf 读入整数时的空字符与越界输入现象运行程序输入了几个数字后程序直接跳过后续输入或者进程编号输入 99 后数组越界、程序闪退。原因scanf 遇到非数字字符会留下脏字符在缓冲区后续 scanf 反复失败进程编号没有做范围检查p[99] 直接写出数组边界。解决进程编号读入后先判断if (pid 0 || pid process_num)再继续。scanf 的返回值可以检查但最简单的做法是规范输入实验报告里预先声明“进程编号从 0 开始”并且代码里对越界做拦截这一条能让程序在验收时显得更健壮。5.5 多资源比较时把 i 和 j 写反现象单资源例子跑得好好的换成三资源例子后结果完全不对而且越跑越乱。原因内层循环比较时写成了if (need[i][j] work[i])把资源和进程维度搞混了。这类错误编译不报错肉眼很难发现。解决强制统一循环命名习惯。外层循环for (int i 0; i process_num; i)里的 i 是进程下标内层for (int j 0; j resource_num; j)里的 j 是资源下标所以一定是need[i][j]和work[j]比allocation[i][j]加到work[j]上。把 i 和 j 的语义写进注释比靠脑子记可靠。6. 进阶给银行家算法加一个随机压力测试壳如果你想让实验报告里的“测试分析”部分比其他同学更有说服力可以加一个小脚本随机生成多组进程和资源数据反复跑 request_resource验证一个关键性质每次拒绝分配后全局状态必须与调用前完全一致每次允许分配后系统仍然安全。这个性质能自动抓住“回滚不完整”和“is_safe 污染全局”这两类最隐蔽的 bug。import random import subprocess def gen_case(): p random.randint(3, 6) r random.randint(2, 4) lines [f{p} {r}] lines.append( .join(str(random.randint(0, 5)) for _ in range(r))) max_matrix [[random.randint(0, 8) for _ in range(r)] for _ in range(p)] alloc_matrix [[min(max_matrix[i][j], random.randint(0, 4)) for j in range(r)] for i in range(p)] for row in max_matrix: lines.append( .join(map(str, row))) for row in alloc_matrix: lines.append( .join(map(str, row))) return \n.join(lines) \n for i in range(50): case gen_case() # 把用例写入文件再调用你的 C 程序 with open(/tmp/banker_test.txt, w, encodingutf-8) as f: f.write(case) subprocess.run([./banker /tmp/banker_test.txt], shellTrue)这个脚本生成的用例保证 Allocation 不超过 Max资源数在 2 到 4 之间进程数在 3 到 6 之间足够覆盖多资源场景。你只需要在 C 程序的主循环里额外加一个“连续自动请求”的隐藏菜单或者直接改用文件重定向输入就能批量跑。生成 50 组用例如果程序中途没有出现状态错乱实验报告的稳定性结论就可以名正言顺地写“经随机压力测试验证”。我自己的习惯是提交实验前必跑一遍这个脚本跑完再把其中一组输出贴进报告并标注这是随机生成的数据不是手工挑出来的。这个细节在验收时被表扬过不止一次。上面提到的所有坑也基本都能靠压力测试暴露出来动手试一遍比记结论有用得多希望帮到你。本文还有配套的精品资源点击获取