动态分区式内存管理(完整代码)
背景知识:
关于内存管理的几种方式:
固定分区:
原理:又称定长分区或静态分区模式,是满足多道程序设计需要的最简单的存储管理技术。基本思想:给进入主存的用户作业划分一块连续存储区域,把作业装入该连续存储区域,若有多个作业装入主存,则它们可并发执行。
使用大小相等的固定分区有两个难点:程序可能太大而不能放到一个分区中,内存的,利用率很低。由于被装入的数据块小于分区大小,从而导致分区内部有浪费现象,成为“内部碎片”。对与大小不等的分区策略,最简单的方法就是把每个进程分配到能够容纳它的最小分区中。
目前已经基本上没有什么场合使用固定分区。
优势:实现简单,只需要极少的操作系统开销
缺点:有内部碎片,对内存的使用不充分,活动进程的最大数目是固定的。
可变分区:
可变分区存储管理不是预先把内存中的用户区域划分成若干固定分区,而是在作业要求装入内存时,根据用户作业的大小和当时内存空间使用情况决定是否为该作业分配一个分区。因此分区大小不是预先固定的,而是按作业需求量来划分的;分区的个数和位置也不是预先确定的。它有效地克服了固定分区方式中,由于分区内部剩余内存空置造成浪费的问题。
可变分区方式常用的内存分配算法有以下几种:
-
1、 最先适应分配算法
每次分配总是顺序查找空闲区表,找到能满足长度要求的空闲区就分配。优点是实现简单,缺点是可能将大的空闲区分割成许多小的空闲区,形成许多不连续的“碎片”。碎片长度可能不能满足作业要求,降低了内存利用率。
改进方法,可把空闲区按地址顺序从小到大登记在空闲区表中,有利于大作业。问题是归还空区时须按地址插入表中适当位置。 -
2、最优适应分配算法
按作业要求从所有空闲区中挑选一个能满足要求的最小空闲区,这样保证不去分割一个更大的区域,使装入大作业时比较容易得到满足。实现办法:将空闲区按长度以递增次序登记在表中,分配时按空闲区表顺序查找即可。缺点是可能碎片更小而无法使用。回收时也要按长度扦入。 -
3、最坏适应分配算法
这种算法总是挑选一个最大的空闲区分割一部分给作业使用,使剩下部分不致太小,仍可供分配使用。实现办法:空闲区表中的登记项按空闲区长度递减顺序排列,按序查找分配。
题目描述:动态分区式存贮区管理
设计一个动态分区式存贮区管理程序,要求支持不同的放置策略。如首次、最佳、最坏。
说明:
(1)分区描述器rd如下:
flag | size | next |
---|
要求空闲区队列按链表组织。
主存大小假设为maxsize(单位为节=rd的大小)。
(2)主程序结构如下:
输入放置策略
申请一块内存作为主存
循环处理用户的请求(包括申请、释放)
需设计两个函数处理用户请求:
- 申请函数 Addr=Request(size)
- 释放函数 Release(addr)
(3)数据实例:maxsize=512
J1申请162,J2申请64,J3申请120,J4申请86,J1完成,
J3完成,J5申请72,J6申请100,J2完成,J7申请36,
J8申请60,J4完成, J9申请110,J10申请42。
备注:
- (a)所有大小其单位为节(1节=rd的大小)
- (b)作业申请n节,实际分配的分区大小应为n+1节。 其中一节作为分区描述器,其他n节提供给作业。
- (c)已分配区放在高地址处。
- (d)合并时应考虑四种情况: 假设回收区为r,上邻为f1(f1需搜索自由主存队列),下邻为f2(f2可直接计算)
A)f1空闲,f2已分配;
B)f1已分配,f2空闲;
C)f1空闲,f2空闲;
D)f1已分配,f2已分配;
设计思路:
-
向系统申请内存:
需要申请的内存单元的个数:totalNum
每个内存单元的大小:sizeof(rd)
程序实际向系统申请到达大小:maxsize
即:
const int totalNum = 512;//需要申请的块数
const int maxsize = totalNum * sizeof(rd);//实际需要申请的大小
rd* MainHead = (rd*)malloc(maxsize);//申请到的资源 -
进程向程序申请内存Request:
原型:rd* Request(int need,int choice);
参数:need表示需要申请的大小,choice表示分配时采取的方法。
返回值:返回一个指针,指向申请到的空间的首地址,若为空则表示申请失败。 -
进程释放内存Release:
原型:bool Release(rd* r);
参数:r表示待释放内存的首地址
返回值:返回1表示释放成功,0表示失败 -
可视化展示内存使用情况:
每一次读取用户的请求后,程序均输出当前的内存使用情况,方便用户查看,包括空闲队列的情况、已用队列的情况、内存总体分配情况。
数据结构:
- 分区描述器:
设计一个结构体rd用来表示分区描述器,其包含:
Flag:分配标志,空闲为0,不为0则表示进程编号
Size:分区大小,分区可用字数+分区描述器大小
*next:指向下一个同类型的区域
struct rd {//分区描述器
int flag = -1; //分配标志,空闲为0,不为0则表示进程编号
int size = -1; //分区大小,分区可用字数+分区描述器大小
rd* next = NULL; //指向下一个同类型的区域
};
- 空闲内存队列和已用内存队列:
两个队列都通过链表的形式组织
分别为其设置头节点Free和Used:
rd* Free = new rd, * Used = new rd;//分别用来存空闲队列和已占用队列
算法设计
三种算法:
首次:按物理地址的顺序排列,优先地址最小的
最佳:按照空闲容量递增排列,优先最小的
最坏:按照空闲容量递减排列,优先最大的
三种算法在分配和释放的操作上没有区别。
区别在于:空闲队列变化以后需要排序,该排序对应的排序算法不同(依据是以上原理)。但在本程序中不使用排序算法,而是每次都遍历搜索满足条件的最值。
进程向程序申请内存Request:
无论是利用上述的哪种方法,都需要从已经给定的头节点开始遍历,找到>=需求的一块空间。
找到满足上述三种算法中最优的内存块:找到内存大小比need大的内存块中地址最小的、内存最小的、内存最大的。
上述最优的内存块分两种情况:
- 内存=need:这种情况只需要在free队列删除该节点,并将其加入used队列即可。
- 内存>need:这种情况需要将原节点信息中的size减小need大小然后在used队列中插入一个新节点。
进程释放内存Release:
释放进程时需要分以下四种情况:假设三块连续内存分别为f1、r、f2
上邻为f1(f1需搜索自由主存队列),下邻为f2(f2可直接计算)
- A)f1空闲,f2已分配; //f和f1合并,修改表项f1,数量不变
- B)f1已分配,f2空闲; //f和f2合并,修改表项f2,数量不变
- C)f1空闲,f2空闲; //f1 f f2合并,修改表项f1,删除f2
- D)f1已分配,f2已分配; //增加空闲表项f
这里设计两个信号量bool f1_is_free = 1, f2_is_free = 1;分别用来判断f1、f2是否已分配,然后程序遍历两个队列正确修改信号量的值后,程序根据这两个值分四种情况对两个队列做出相应的修改即可。
可视化展示内存使用情况:
首先遍历输出两个队列的以下信息:
- 1.头节点相对于总内存起点的偏移量
- 2.该段内存实际大小
由于本程序中插入队列元素时没有对其排序,上述输出不直观,所以继续采用以下方法输出:
由内存的起点开始向后同时遍历两个队列,每次找到与之最相邻的那个内存块。
找到内存块后由flag判断其是否已占用,由size判断这块内存的大小。
输出头节点相对于总内存起点的偏移量然后输出不同数量的符号表示这块空间
原则:未占用用‘-’表示,已占用用‘+’表示
输出的数量(长度)与内存大小成正比。
输出示例:
整体实现的代码:
#include <iostream>
#include <fstream>
using namespace std;
struct rd {//分区描述器
int flag = -1; //分配标志,空闲为0,//不为0则表示进程编号
int size = -1; //分区大小,分区可用字数+分区描述器大小
rd* next = NULL; //对空闲区:指向下一个空闲分区,对已分配区:此项为零。
};
rd* Free = new rd, * Used = new rd;//分别用来存空闲队列和已占用队列
const int totalNum = 512;//需要申请的块数
const int maxsize = 512 * sizeof(rd);//实际需要申请的大小
rd* MainHead = (rd*)malloc(maxsize);//申请到的资源
rd* Request(int need,int choice) {//申请地址的函数,返回申请到的地址,为NULL则无法申请。
//size为要申请的danwei大小的个数,实际大小为size* sizeof(rd);
int tem;
rd* Ans = NULL;
rd* p = NULL, * prep = new rd, * preAns = new rd;
if (choice == 1) {//首次算法:按物理地址的顺序排列,优先地址最小的
tem = INT_MAX;
for (p = Free->next, prep->next = p; p != NULL; p = p->next, prep = prep->next)
if ((int)p < tem && p->size > need)
tem = (int)p, Ans = p, preAns = prep;
if (Ans == NULL)
return NULL;
}
else if (choice == 2) {//最佳算法按:照空闲容量递增排列,优先最小的
tem = totalNum;
for (p = Free->next, prep->next = p; p != NULL; p=p->next, prep= prep->next)
if (p->size < tem && p->size > need)
tem = p->size, Ans = p, preAns = prep;
if (Ans == NULL)
return NULL;
}
else if (choice == 3) {//最坏算法:按照空闲容量递减排列,优先最大的
tem = 0;
for (p = Free->next, prep->next = p; p != NULL; p = p->next, prep = prep->next)
if (p->size > tem)
tem = p->size, Ans = p, preAns = prep;
if (tem < need)
return NULL;
}
//修改free队列
if (Ans->size == need)
preAns->next = Ans->next;//删除表项
else {
Ans->size -= need;
//Ans =(Ans+Ans->size * sizeof(rd));//后一半划分出去
// cout<<"当前分配地址距离开始" << Ans - MainHead << endl;
Ans += Ans->size;
}
Ans->flag = 1;
Ans->size = need;
//修改used队列
if (Used->next == NULL) {
Used->next = Ans;
Ans->next = NULL;
} else {
Ans->next = Used->next;
Used->next = Ans;
}
return Ans;
}
bool Release(rd* r) {//释放所给出的分区的地址,返回1表示正常释放
if (r->flag == 0) {
cout << "该空间未使用,无法释放" << endl;
return 0;
}
rd* p = NULL;
rd* f1 = NULL, * f2 = NULL;
//先判断used
for (p = Used->next; p != NULL; p = p->next) {
if (p->next == r)
f1 = p;
if (r->next == p)
f2 = p;
}
//再判断free
for (p = Free->next; p != NULL; p = p->next) {
if (p+p->size == r)
f1 = p;
if (r+r->size == p)
f2 = p;
}
//修改used队列
for (p = Used->next; p != NULL; p = p->next)
if (p->next == r) {
p->next = r->next;
break;
}
bool f1_is_free = 1, f2_is_free = 1;
if (f1->flag)
f1_is_free = 0;
if (f2 == NULL || f2->flag)
f2_is_free = 0;
//修改free队列
if (f1_is_free && !f2_is_free) { // A)f1空闲,f2已分配; //f和f1合并,修改表项f1,数量不变
f1->next = r->next;
f1->size += r->size;
}
else if (!f1_is_free && f2_is_free) { // B)f1已分配,f2空闲; //f和f2合并,修改表项f2,数量不变
r->next = f2->next;
r->flag = 0;
r->size += f2->size;
for (p = Free; p != NULL; p = p->next)//找到f2的前驱
if (p->next == f2) {
p->next = r;//后继修改为r
break;
}
}
else if (f1_is_free && f2_is_free) { // C)f1空闲,f2空闲; //f1 f f2合并,修改表项f1,删除f2
for (p = Free; p != NULL; p = p->next)//找到f1的前驱
if (p->next == f1)
break;
if (p != f2->next)//防止构成环路
f1->next = f2->next;
f1->size += (f2->size+r->size);
for (p = Free; p != NULL; p = p->next)//找到f2的前驱
if (p->next == f2) {
p->next = f2->next;//后继修改为r
break;
}
}
else if (!f1_is_free && !f2_is_free) {// D)f1已分配,f2已分配; //增加空闲表项f
r->flag = 0;
if (Free->next == NULL) {
Free->next = r;
r->next = NULL;
}
else {
r->next = Free->next;
Free->next = r;
}
}
return 1;
}
void show_detail() {//展示内存的使用情况
rd* p = NULL;
rd* Cur = MainHead;
//先判断used
cout <<"Used:--";
for (p = Used->next; p != NULL; p = p->next) {
cout << p - MainHead <<"_"<<p->size<< "--->";
}
cout << endl;
//再判断free
cout << "Free:--";
for (p = Free->next; p != NULL; p = p->next) {
cout << p - MainHead << "_" << p->size << "--->";
}
cout << endl;
do{//直到当前是最后一块为止
for (p = Free->next; p != NULL; p = p->next)
if (Cur == p) {
break;
}
for (p = Used->next; p != NULL; p = p->next)
if (Cur == p) {
break;
}
//到此就找到了上一次的Cur后跟的块(free或者used)
//输出可视化
//512个字节,共分为26段,已用用+++++表示,未用用======表示,每段用三个符号表示
string u = "+++";
string f = "---";
if (Cur->flag == 0) {//为free
// cout << Cur->size;
cout << Cur - MainHead;
for (int i = 0; i < (Cur->size / 20); i++)
cout << f;
}
if (Cur->flag != 0) {//为free
cout << Cur - MainHead;
for (int i = 0; i < (Cur->size / 20); i++)
cout << u;
}
Cur += Cur->size;
} while ((Cur - MainHead ) < totalNum-1);
cout << "|||";
cout << endl << endl << endl << endl;
}
int main()
{
while (1) {
cout << "——————————请输入放置策略————————" << endl;
cout << "—— 首次:1 最佳:2 最坏:3 退出:0——" << endl;
cout << "—————————————————————————" << endl;
int method ;
cin >> method;
if(method==0) {
system("cls");
cout << "——————————Thanks!————————" << endl;
return 0;
}
if (method != 1 && method != 2 && method != 3) {
cout << "您的输入有误,请重新输入:" << endl;
continue;
}
MainHead = (rd*)malloc(maxsize);//申请到的资源
Free = new rd, Used = new rd;//分别用来存空闲队列和已占用队列
MainHead->next = NULL;
rd* fir = MainHead;fir->next = NULL; fir->flag = 0; fir->size = totalNum - 1;
Free->next = fir;
char operat;
int id, need;
fstream In;
In.open("请求.txt");
if (!In.is_open())
cout << "文件打开失败" << endl;
cout << "初始状态:" << endl; show_detail();//展示初始状态
while (!In.eof()) {
In >> operat;
if (operat == '+') {
In >> id >> need;
rd* req = Request(need+1, method);//req为申请到的地址
if (req == NULL) {
cout << " 失败___" << id << " +++ " << need << " " << endl;
break;
}
cout << " 成功___" <<id<< " +++ " << need << " " << endl;
req->flag = id;
req->size = need+1;
}
else
if (operat == '-') {
In >> id ;
rd* rel = Used->next;
for (; rel != NULL; rel=rel->next)
if (rel->flag == id)
break;
int rel_ans = 1;
int rel_size = rel->size;
if (rel == Used->next) {
cout << "进程" << id << "没有正在使用的空间" << endl;
break;
}
else
rel_ans = Release(rel);
if (rel_ans == 0) {
cout << "进程___" << id << "无法释放" << endl;
}
else
cout << " 成功___" << id << " --- " << rel_size << " " << endl;
}
else {
cout << operat << "读取失败" << endl;
break;
}
show_detail();//展示每次修改后的结果
}
free(MainHead);
}
return 0;
}
程序运行情况:
测试数据:
运行截图:
- 方法二:最佳优先算法