模拟动态分区分配算法(友好用户界面显示、图形化交互界面、可视化、操作系统作业)
一、 前言
编写一个内存动态分区分配模拟程序,分别实现:首次适应、循环首次适应、最佳适应算法,对内存的分配和回收过程,此程序没有实现“紧凑”。
每次分配和回收后把空闲分区的变化情况以及进程的申请、释放情况最好以图形方式显示,尽可能设计一个友好的用户界面,直观显示内存区域经分配、回收的动态变化情况。
二、实验结果
1、首次适应算法-程序执行结果演示
-
进行分配算法测试数据:(进程-1表示此内存块是空闲区,没有进程占用) 申请空间的进程顺序及大小:
程序执行结果
【进行内存回收的测试:】进行内存回收的测试数据:(因为回收内存时,不需要内存空间大小数据,当点击确定按钮后,会检测数据是否合法,即内存中是否有此进程的id,若合法则进行回收算法,并置内存空间大小的textbox为空)
释放的(内存块/进程)序号的顺序为:1、0、2、4、3
进程1: 前后没有空闲区 ,没有合并空闲区操作
进程0: 后有空闲区——>所以0、1空闲区合并一个空闲区
进程2: 前有(0、1合并后的)空闲区——>所以(0、1)与4和并一个空闲区
进程4: 前后没有空闲区
进程3: 前(有0、1、2合并后的空闲区)后(有空闲区4)都有空闲区 ,合并成一个大空闲区(0、1、2、3、4) -
程序执行结果
【注:释放已经释放的进程空间会提示错误,如,下图,进程1已经释成功了,再点击确定按钮,想要释放就提示错误】
【释放内存后再申请空间去测试,首次适应算法是从内存开始位置进行申请。如,在上图测试之后,进程6 申请内存 20 M,存在两个空闲区,如下图】
【 但首次适应算法,会选择另一个内存块,结果如下,所以首次适应算法演示成功】
2、循环首次算法-程序执行结果演示
- 建议循环首次算法测试数据为:
【 回收过程:回收进程4的内存“】
【回收进程2的内存:】
【此时空闲区有:25MB(4原来的释放区) 、28MB(2原来的释放区)、18MB(以及剩余的从未用过的空闲区)】
【分配:进程6 24MB 按空闲区递增排序,选择起始地址为33MB的内存块】
【回收6进程,分配:7进程 23MB】
【此时,3个空闲区如表,但起始地址为33MB大小的空闲区已经分配过了,所以会在之后的量的空闲区选择,如图,得以说明,循环首次适应算法模拟成功】
3、最佳适应算法-程序执行结果演示
与上类似,且可以各算法切换测试。
三、算法:
1、采用的数据结构
采用的数据结构:链表而非分区表表示内存使用情况。自定义Nodespace类用于记录每个内存块的使用情况包括空闲块和占用块。因为C#里没有指针,所以为实现链表的功能,采用C#的list类实现,Removeat():进行空闲块的合并(即为空闲块的删除)、Insert():进程申请内存空间成功把新内存块信息加入到链表,等等方法、、、、
public class Nodespace
{
public int taskid; // 任务号
public int begin; // 开始地址
public int size; // 大小
public int status; // 状态 0代表占用,1代表空闲
public Nodespace(int taskid, int begin, int size, int status)
{
this.taskid = taskid;
this.begin = begin;
this.size = size;
this.status = status;
}
2、首次适应算法
- 注: public static List ls = new List();
c#中Listfind的使用参考链接如下,总结的很简单,截图如下
c#中List < T > find使用示例
- 函数完整代码:
#region 首次适应算法
public bool FirstPartition(int taskid,int size)
{
int index = ls.FindIndex(param => param.size >= size && param.status == 1);
if (index != -1)
{
if (ls[index].size > size)
{
//int teskid, int begin, int size, int status
Nodespace t = new Nodespace(taskid, ls[index].begin, size, 0);
ls[index].begin += size;
ls[index].size -= size;
ls.Insert(index, t);
}
else
{
ls[index].status = 0;
ls[index].size = size;
ls[index].taskid = taskid;
}
}
else
{
return false;
}
return true;
}
#endregion
3、循环首次适应算法
- 注:查找在list中从startIndex(全局变量,初始值0)到list.ount大于申请的size的空闲内存块,若没找到,再从0~startIndex找
- 每一次切换到循环首次算法后,都要把startIndex = 0;(我认为各内存分配算法,只是在分配时分配方法不一样,free内存释放过程一样,所以此程序中途是可以切换使用分配算法的)
private void rb_cyclepart_CheckedChanged(object sender, EventArgs e)
{
startIndex = 0;
}
- 函数完整代码:
#region 循环首次适应算法
public bool CyclyFirstPartition(int taskid, int size)
{
int index = ls.FindIndex(startIndex,param => param.size >= size && param.status == 1);
if (index == -1)
index = ls.FindIndex(0, startIndex, param => param.size >= size && param.status == 1);
if (index != -1)
{
if (ls[index].size == size)
{
ls[index].status = 0;
ls[index].taskid = taskid;
}
else
{
Nodespace t = new Nodespace(taskid, ls[index].begin, size, 0);
ls[index].begin += size;
ls[index].size -= size;
ls.Insert(index, t);
startIndex = (index+1)%ls.Count;
}
return true;
}
else
{
return false;
}
}
#endregion
4、最佳适应算法
- C#中List.Sort()–集合排序方法有四种,我选用方法一、对Nodespace类继承IComparable接口,实现CompareTo()方法,参考文章链接及截图如下,
C#中List.Sort()–集合排序方法分析
- 之所以设置两个排序类,因为分配时,空闲区按size大小从小到大排序从而找到最适宜的大小的空闲内存块;回收时,空闲区按开始地址排序,查找释放区的前后有无空闲区从而对应着释放空间时的4种情况(如图1),从而有空闲区进行合并的不同方式。但用NodespaceCompare_partition类进行size从小到大的排序后,后续的释放会打乱原有内存块地址递增的顺序。我在写的时候没想到这么做,出现图2所示的出现画不出内存块从而变白色,以及超过绘图范围0~200MB的情况、各种小问题。
图1:
图2:
//分配时,空闲区按size大小从小到大排序
public class NodespaceCompare_partition : IComparer<Nodespace>
{
public int Compare(Nodespace x, Nodespace y)
{
return (x.size.CompareTo(y.size));
}
}
//回收时,空闲区按开始地址排序
public class NodespaceCompare_free : IComparer<Nodespace>
{
public int Compare(Nodespace x, Nodespace y)
{
return (x.begin.CompareTo(y.begin));
}
- 函数完整代码:
#region 最佳适应
public bool BestPartiton(int taskid, int size) {
//因为这三个算法目的只是选择合适的内存空间,所以中间可以切换执行算法,从而不能确保执行最佳算法之前也执行的是执行最佳算法。从而无法确保他是有序的
ls.Sort(new NodespaceCompare_partition());
int index = ls.FindIndex(param => param.size >= size && param.status == 1);
if (index!= -1)
{
if (ls[index].size == size)
{
ls[index].status = 0;
ls[index].taskid = taskid;
}
else
{
Nodespace t = new Nodespace(taskid, ls[index].begin, size, 0);
ls[index].begin += size;
ls[index].size -= size;
ls.Insert(index,t);
}
return true;
}
return false;
}
#endregion
5、点击开始按钮-进行界面和数据的初始化
-
初始界面:程序默认选择分配内存,首次适应算法
如图
-
数据初始化后界面,即点击开始按钮进行数据初始化,清空内存块的进程。(点击开始按钮如下界面,左、右侧表示的内存块是(0~200MB大小的内存),左侧表示进行分配或回收的算法之前的内存状况,右侧是进行分配或回收的算法之后的内存状况)
如图 -
使用4个piicturebox,一个listview,分别对应着程序中的画图部分的图一、图二、图三、图四,图四,以及右边的表格。图四初始后固定,图1、2、3每次执行(点击确定按钮,并检测输入数据合法后执行)都会重绘
-
开始画图的时候,我不知道原来在bitmap中画,只要考虑相对位置就好了(因为bitmap放到picturebox,picturebox本身就带有坐标位置),就产生了四个画板什么都没有的情况,而且相对位置画这构图时简单了好多
-
清空并初始化数据:
ls.Clear();
//加入进程名-1,起始位置0,size=200,状态1的内存块
Nodespace nodespace = new Nodespace(-1, 0, 200, 1);
ls.Add(nodespace);
- 绘图中,我觉得我的表示内存块的矩形太丑了,出现了只有右、下有边情况
简单的更改方法: 先fill再draw.后来我觉得是画笔对齐 PenAlignment方式问题,没空就不测试了。这个参考链接的作者在写GDI学习记录系列的文章特别简单、扼要、易懂,值得学习:GDI+学习记录(4)-画笔对齐 PenAlignment
g3.FillRectangle(sbs[0], r1);
g3.DrawRectangle(penBlack, r1);
如图
- 函数完整代码:
#region 开始按钮
private void bt_start_Click(object sender, EventArgs e)
{
ls.Clear();
Nodespace nodespace = new Nodespace(-1, 0, 200, 1);
ls.Add(nodespace);
Graphics g12 = pb_mem1.CreateGraphics();
g12.Clear(Color.White);
Graphics g22 = pb_mem2.CreateGraphics();
g22.Clear(Color.White);
Graphics g32 = pb_mem3.CreateGraphics();
g32.Clear(Color.White);
Bitmap b1 = new Bitmap(200, 480);
Graphics g1 = Graphics.FromImage(b1);
Rectangle r1 = new Rectangle(30,40,90,ls[0].size);
g1.FillRectangle(sbs[0], r1);
g1.DrawRectangle(penBlack, r1);
g1.DrawString("0MB", myfont, sbs[3], new Point(0, 30));
g1.DrawString("200MB", myfont, sbs[3], new Point(0, 240));
g12.DrawImage(b1, 0, 0);
Bitmap b3 = new Bitmap(200, 480);
Graphics g3 = Graphics.FromImage(b3);
g3.FillRectangle(sbs[0], r1);
g3.DrawRectangle(penBlack, r1);
g3.DrawString("0MB", myfont, sbs[3], new Point(0, 30));
g3.DrawString("200MB", myfont, sbs[3], new Point(0, 240));
g32.DrawImage(b3, 0, 0);
//free 图1
b1.Dispose();
b1 = null;
g1.Dispose();
g1 = null;
g12.Dispose();
g12 = null;
//free 图3
b3.Dispose();
b3