操作系统实验报告
实验三:主存空间的分配与回收
一、实验题目
采用可变式分区管理,使用最佳适应算法实现主存的分配与回收。
二、实验内容
主存是中央处理机能直接存取指令和数据的存储器。能否合理而有效地使用主存,在很大程度上将影响到整个计算机系统的性能。本实验采用可变式分区管理,使用首次或最佳适应算法实现主存空间的分配与回收。要求采用分区说明表进行。
三、实验目的
通过本次实验,帮助学生理解可变式分区管理方式下,如何实现主存空间的分配与回收。
提示:
(1)可变式分区管理是指在处理作业过程中建立分区,使分区大小正好适合作业的需要,并且分区个数是可以调整的。当要装入一个作业时,根据作业需要的主存量,查看是否有足够的空闲空间,若有,则按需求量分割一部分给作业;若无,则作业等待。随着作业的装入、完成,主存空间被分割成许多大大小小的分区。有的分区被作业占用,有的分区空闲。例如,某时刻主存空间占用情况如图1所示。
为了说明哪些分区是空闲的,可以用来装入新的作业,必须要有一张空闲区说明表,如表1所示。其中,起始地址指出各空闲区的主存起始地址,长度指出空闲区大小。状态栏未分配指该栏目是记录的有效空闲区,空表目指没有登记信息。由于分区个数不定,所以空闲区说明表中应有足够的空表目项,否则造成溢出,无法登记。同样,再设一个已分配区表,记录作业或进城的主存占用情况。
(2)当有一个新作业要求装入主存时,必须查空闲区说明表,从中找出一个足够大的空闲区。有时找到的空闲区可能大于作业需求量,这时应该将空闲区一分为二。一个分给作业,另一个仍作为空闲区留在空闲区表中。为了尽量减少由于分割造成的碎片,尽可能分配低地址部分的空闲区,将较大空闲区留在高地址端,以利于大作业的装入。为此在空闲区表中,按空闲区首地址从低到高进行登记。为了便于快速查找,要不断地对表格进行紧缩,即让“空表目”项留在表的后部。其分配框图如图2所示。
(3)当一个作业执行完时,作业所占用的分区应归还给系统。在归还时要考虑相邻空闲区合并的问题。作业的释放区与空闲区的邻接分以下4种情况考虑:
- 释放区下邻(低地址邻接)空闲区;
- 释放区上邻(高地址邻接)空闲区;
- 释放区上下都与空闲区邻接;
- 释放区与空闲区不邻接。
- 首次适应算法回收框图如图3所示。
-
若采用最佳适应算法,则空闲区说明表中的空闲区按其大小排序。有关最佳适应算法的分配和回收框图由学生自己给出。
(4)请按首次(或最佳)适应算法设计主存分配和回收程序。以图1作为主存当前使用的基础,初始化空闲区和已分配区说明表的值。学生自己设计一个作业申请队列以及作业完成后的释放顺序,实现主存的分配与回收。把空闲区说明表的变化情况以及各作业的申请、释放情况显示或打印出来。
为了说明哪些分区是空闲的,必须要有一张空闲区说明表,格式如下表所示:
起始地址 | 长度 | 状态 |
20K | 20K | 1 |
80K | 50K | 1 |
150K | 100K | 1 |
300K | 30K | 0(空表目) |
600K | 100K | 1 |
… | … | 空表目 |
… | … | … |
四、实验过程
4.1 最佳适应算法的分配流程图
最佳适应算法的分配流程图如下图所示,在首次适应算法的基础上增加了对空闲分区的重新排序流程,总是把满足要求且为最小的空闲分区分配给作业。
图4.1 最佳适应算法分配流程图
4.2 最佳适应算法的回收流程图
最佳适应算法与首次适应算法的区别在于,最佳适应算法的空闲区说明表中的空闲区是按大小排序的。因此最佳适应算法的回收流程图与首次适应算法的回收流程图大同小异,具体如下图所示。
图4.2 最佳适应算法释放流程图
五、代码及运行结果分析
5.1 代码
使用最佳适应算法设计主存的分配和回收程序,代码如下所示,以图4.1作为主存当前使用的基础,设计了自动作业申请队列和释放顺序,能够实现主存的分配与回收。同时打印出了对于空闲区说明表的变化情况和各作业的申请、释放情况。
#include <stdio.h>
#include <stdlib.h>
#include<algorithm>
#include <string.h>
#include <iostream>
#include <windows.h>
#define MAX 512 // 内存总大小为512K
using namespace std;
const int pLength = 8; // 最大分区数
typedef struct Partition{
char pName[20]; // 内存分区名字
int start; // 起始地址
int size; // 地址空间大小
bool status; // 内存分区状态,true为空表
};
Partition partitions[pLength]; // 内存分区最多8个
Partition partitions_All[pLength];
const int aLength = 5;
typedef struct Assignment{
int uid; // 作业编号
char aName[20]; // 作业名字
int asize; // 作业大小
int startAddress; // 作业起始地址
int arriveTime; // 到达时间
int startTime; // 开始工作时间
int runTime; // 工作时间
Assignment *next; // 链表连接
};
Assignment assignments[aLength]; // 作业
Assignment* waitA; // 等待工作
int endCount;
int clockTime;
int sequence[aLength];
int flag; // 全局变量记录回收类型
// 判断大小
bool cmp(Partition a, Partition b) {
return a.size < b.size;
}
// 单分区情况
void outPut(int i){
printf("%s\t\t%d\t\t\t%d\t\t%d\n", partitions[i].pName, partitions[i].start, partitions[i].size, partitions[i].status);
}
void printInfo(){
printf("Name\t\tStart_Address\t\tSize\t\tStatus\n");
for(int i = 0; i < pLength; i++){
printf("%s\t\t%d\t\t\t%d\t\t%d\n", partitions_All[i].pName, partitions_All[i].start, partitions_All[i].size, partitions_All[i].status);
}
}
void init(){
strcpy(partitions_All[0].pName, "OS");
partitions_All[0].start = 0; // 起始地址0,长度10
partitions_All[0].size = 10;
partitions_All[0].status = false;
strcpy(partitions_All[1].pName, "TEST1");
partitions_All[1].start = 10; // 起始地址10,长度10
partitions_All[1].size = 10;
partitions_All[1].status = false;
strcpy(partitions_All[2].pName, "TEST4");
partitions_All[2].start = 20; // 起始地址20,长度25
partitions_All[2].size = 25;
partitions_All[2].status = false;
strcpy(partitions_All[3].pName, "---");
partitions_All[3].start = 45; // 起始地址45,长度20
partitions_All[3].size = 20;
partitions_All[3].status = true;
strcpy(partitions_All[4].pName, "TEST2");
partitions_All[4].start = 65; // 起始地址65,长度45
partitions_All[4].size = 45;
partitions_All[4].status = false;
strcpy(partitions_All[5].pName, "---");
partitions_All[5].start = 110; // 起始地址110,长度40
partitions_All[5].size = 40;
partitions_All[5].status = true;
strcpy(partitions_All[6].pName, "---");
partitions_All[6].start = 150; // 起始地址150,长度40
partitions_All[6].size = 40;
partitions_All[6].status = true;
strcpy(partitions_All[7].pName, "---");
partitions_All[7].start = 190; // 起始地址180,长度45
partitions_All[7].size = 45;
partitions_All[7].status = true;
int count = 0; // 提取空闲区
for(int i = 0; i < pLength; i++){
if(partitions_All[i].status == true) {
partitions[count] = partitions_All[i];
count++;
}
}
for(int i = count; i < pLength; i++){
partitions[i].status = false;
partitions[i].size = INT_MAX;
}
printf("==========================================================\n");
printf("| 欢迎使用最佳适应算法测试系统 |\n");
printf("| 状态1为盈余空间,状态0为已被占用! |\n");
printf("| |\n");
printf("==========================================================\n\n");
printf("初始化内存使用情况:\n");
printInfo();
printf("\n初始化作业队列:\n");
printf("Uid\t\tAssignment_Size\t\tArriveTime\tRunTime\n");
srand(GetTickCount());
for(int i = 0; i < 5; i++){
assignments[i].uid = i + 1;
assignments[i].asize = ((float) rand()/32767) * 40 + 10;
assignments[i].arriveTime = ((float) rand()/32767) * 20;
assignments[i].runTime = ((float) rand()/32767) * 20;
assignments[i].startTime = -INT_MAX;
printf("%d\t\t%d\t\t\t%d\t\t%d\n", assignments[i].uid, assignments[i].asize, assignments[i].arriveTime, assignments[i].runTime);
}
}
// 展示内存空闲分区情况
void display() {
printf("\tStart_Address\t\tSize\t\tStatus\n");
for(int i = 0; i < pLength; i++){
// 占用
if(partitions[i].status == false) {
printf("\t---\t\t\t---\t\t0\n");
} else{
printf("\t%d\t\t\t%d\t\t%d\n", partitions[i].start, partitions[i].size, partitions[i].status);
}
}
}
// 分配内存
bool distribute(int pos){
// 申请主存
for(int i = 0; i < pLength; i++) {
// 未分配吗?
if(partitions[i].status == false)
return false;
// 长度大于等于作业
if(partitions[i].size >= assignments[pos].asize) {
printf("分配空间成功!\n");
printf("作业序号:%d\n", assignments[pos].uid);
printf("作业大小:%d\n", assignments[pos].asize);
printf("到达时间:%d\n", assignments[pos].arriveTime);
printf("工作时间:%d\n", assignments[pos].runTime);
assignments[pos].startTime = clockTime; // 启动时间为时钟时间
assignments[pos].startAddress = partitions[i].start; // 分配内存起始地址
// 置状态为空表目
if(partitions[i].size == assignments[pos].asize) {
partitions[i].status = false; // 不空
partitions[i].size = INT_MAX;
strcpy(partitions[i].pName, "***");
} else {
// 长度-= 地址+=
partitions[i].start += assignments[pos].asize;
partitions[i].size -= assignments[pos].asize;
}
// 将长度已经减少的表目向前排序,使空闲区表按大小排序
sort(partitions, partitions + pLength, cmp);
printf("分配后的空闲分区使用情况:\n");
display();
//printInfo();
return true;
}
}
return false;
}
// 释放内存
void remove(int pos) {
flag = 0;
for(int i = 0; i < pLength; i++){
if(partitions[i].status == false) {
partitions[i].start = assignments[pos].startAddress;
partitions[i].size = assignments[pos].asize;
partitions[i].status = true; // 未使用状态
flag = 1; // 上下均无邻接
break;
}
if(i < 99 && partitions[i].start + partitions[i].size == assignments[pos].startAddress &&
partitions[i+1].start == assignments[pos].startAddress + assignments[pos].asize) { // 上下衔接情况
partitions[i].size += partitions[i+1].size + assignments[pos].asize; // 合并情况
partitions[i+1].status = false; // 未使用状态
partitions[i+1].size = INT_MAX; // 置空
flag = 2; // 与上下空闲区邻接情况
break;
} else if(partitions[i].start + partitions[i].size == assignments[pos].startAddress) { // 上衔接情况
partitions[i].size += assignments[pos].asize;
flag = 3; // 与上空闲区邻接情况
break;
} else if(partitions[i].start == assignments[pos].startAddress + assignments[pos].asize) {
partitions[i].start = assignments[pos].startAddress; // 下衔接情况
partitions[i].size += assignments[pos].asize;
flag = 4; // 与下空闲区邻接情况
break;
}
}
printf("回收空间成功!");
printf("作业序号:%d\n", assignments[pos].uid);
printf("作业大小:%d\n", assignments[pos].asize);
printf("到达时间:%d\n", assignments[pos].arriveTime);
printf("工作时间:%d\n", assignments[pos].runTime);
switch (flag) {
case 0:
printf("ERROR!\n");
break;
case 1:
printf("\t释放区与空闲区不邻接!\n");
break;
case 2:
printf("\t释放区上下都与空闲区邻接!\n");
break;
case 3:
printf("\t释放区与上空闲区邻接!\n");
break;
case 4:
printf("\t释放区与下空闲区邻接!\n");
break;
}
// 将长度已经减少的表目向前排序,使空闲区表按大小排序
sort(partitions, partitions + pLength, cmp);
printf("回收后的空闲分区使用情况:\n");
display();
//printInfo();
endCount++; // 结束
}
// 工作区
void Test(){
clockTime = 0; // 时间为0
endCount = 0; // 没有结束工作
waitA = NULL;
int index = 0; // 定义作业释放顺序下标
while(endCount < aLength){ // 作业未完成循环检测
printf("====================================================\n");
printf("时钟:%d\n", clockTime);
printf("空闲区说明表:\n");
display();
//printInfo();
for(int i = 0; i < aLength; i++){
if(assignments[i].arriveTime == clockTime){ // 作业是否到达?
if(waitA == NULL){ // 当前没有等待
assignments[i].next = NULL; // 队列链接 ——直接放入
waitA = &assignments[i];
} else { // 当前有等待的队列作业
Assignment *p = waitA; // 队列迁移
while(p->next != NULL){ // 寻找队尾
p=p->next;
}
assignments[i].next = NULL;
p->next = &assignments[i];
}
}
}
// 存在等待队列的情况
if(waitA != NULL) {
Assignment *p = waitA;
// 检测是否有允许分配的
while(p->next != NULL) {
if (distribute(p->next->uid-1) == true) { // 允许分配
p->next = p->next->next;
} else {
p = p->next;
}
}
if (distribute(waitA->uid-1) == true) { // 当前可以工作
waitA = NULL;
}
}
for(int i = 0; i < aLength; i++) {
if(assignments[i].startTime + assignments[i].runTime == clockTime) {
remove(i); // 释放作业
sequence[index] = assignments[i].uid;
index++;
}
}
clockTime++;
}
}
void printSequence() {
printf("\n系统作业释放顺序为:");
for(int i = 0; i < aLength; i++){
if(i != aLength - 1){
printf("%d->", sequence[i]);
} else{
printf("%d\n", sequence[i]);
}
}
}
int main() {
init();
Test();
printSequence();
}
5.2 运行结果及分析
5.2.1 初始化内存以及作业列表
系统模拟题给图1,利用init()函数初始化内存使用情况,构造了8块内存空间,其中第0、1、2、4块内存分别被OS、TEST1、TEST4、TEST2任务占用,起始地址分别为0、10、20、65,空间大小分别为10、10、25、45,状态为0代表已经被占用,剩余4块空闲内存起始地址分别为45、110、150、190,空间大小分别为20、40、40、45,状态为1代表有盈余空间。
同时在init()初始化函数内通过rand()函数随机生成aLength(当前情况下aLength==5)个作业,作业包含了作业编号、作业大小、作业到达时间、作业运行时间信息。运行结果如下所示。
5.2.2 分配验证及分析
根据作业队列情况,时钟1情况下,作业3抵达。作业3大小为44K,运行时间为0。按照最佳适应算法分配空间区给该作业,空闲区说明表中的第四个空闲分区满足作业3的大小,故空闲区中的第四个空间起始地址向后挪动44个单位,大小减少44K。由于上述空间分配给作业3,故大小仅剩1K,空闲区说明表按照空闲区大小进行重新排序。运行结果如下图所示。
时钟2~6情况下,暂时没有作业到达,打印出的空闲区说明表没有变化,由于上述作业在抵达后立即释放,故当前空闲区情况与初始化后均一致。
5.2.3 回收验证及分析
在时钟7情况下,作业3工作结束。按照最佳适应算法释放算法,进行空间回收,由于作业的终止地址与起始地址为234的空闲邻接,故空闲区第一个空间的起始地址修改为作业开始地址(向前挪动44个单位),大小在原有的基础上增加了44K的作业大小。空闲区说明表则按照空闲大小进行重新排序。具体结果如下所示。
5.2.4 系统释放作业顺序队列
最终作业释放顺序如下所示,验算后符合最佳适应算法释放算法,证明实验成功。
六、心得体会
本次实验的内容是使用最佳适应算法实现主存的分配与回收。
在代码实现中,通过自动生成内存空间、作业队列,利用最佳适应算法的原理,成功实现了作业的动态分配与回收。其中,回收分别考虑了以下四种情况:释放区与上空闲区邻接、释放区与下空闲区邻接、释放区与上下空闲区邻接、释放区与上下空闲区均不邻接情况;并通过多次执行测试,验证程序满足上述四种情况的事件发生。
整体来说,本次分配与回收的算法难度并不大。根据老师给的实验方案中的首次适应算法的分配和回收流程图,绘制最佳适应算法的分配与回收流程图,逐步实现代码。同时初始化内存空间和作业队列也并不困难,难度在于如何模拟CPU实现动态按照时钟序列输出作业的分配与释放。在这里我设计了test()函数模拟CPU执行情况,作业按照队列的数据结构与时钟进行判断,若作业在抵达时加入队列指针并按最佳适应算法分配运行,同时系统不断循环检测判断作业是否到了完成时间,若完成则移出队列,并记录顺序。故在主函数中只需要调用init初始化函数、test模拟CPU函数、printSequence输出释放顺序函数,即可完成全程的主存分配与回收。
每次生成的作业队列大小、到达时间、运行时间均不相同,也可以修改内存空间大小,故程序的泛性和通用性较强,能较好解释最佳适应算法;并且更好地帮助我理解可变式分区管理,它的实现比较简单但是每次分配后所切割下来的部分总是最小的,也容易形成难以利用的碎片。
总之本次实验对于程序设计、主存空间管理、作业队列等都有较大帮助,让我对于操作系统理解更深。