实验七 动态分区分配方式的模拟
一、实验目的
了解动态分区分配方式中使用的数据结构和分配算法,并进一步加深对动态分区存储管理方式及其实现过程的理解。
二、实验环境
硬件环境:计算机一台,局域网环境;
软件环境: Windows或Linux操作系统, C语言编程环境。
三、实验内容
1、用C语言分别实现采用首次适应算法和最佳适应算法的动态分区分配过程alloc( )和回收过程free( )。其中,空闲分区通过空闲分区链来管理:在进行内存分配时,系统优先使用空闲区低端的空间。
2、假设初始状态下,可用的内存空间为640KB,并有下列的请求序列:
•作业1申请130KB。
•作业2申请60KB。
•作业3申请100KB。
•作业2释放60KB。
•作业4申请200KB。
•作业3释放100KB。
•作业1释放130KB。
•作业5申请140KB。
•作业6申请60KB。
•作业7申请50KB。
•作业6释放60KB。
请分别采用首次适应算法和最佳适应算法,对内存块进行分配和回收,要求每次分配和回收后显示出空闲分区链的情况。
3、实验报告要求:
(1)给出具体的设计过程,贴出相应的代码,截图给出试验结果。
(2)结合实验情况,谈谈你对存储管理的理解和体会。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
struct Jobs
{
int jobID;
int addr_start;
int addr_end;
struct Jobs *next;
};
struct Unoccupied_block
{
struct Unoccupied_block *previous;
int addr_start;
int addr_end;
struct Unoccupied_block *next;
};
struct Blocking_queue
{
int jobID;
int space;
struct Blocking_queue *next;
};
struct Jobs *jobhead;
struct Blocking_queue *bqhead;
struct Unoccupied_block *ubhead;
bool empty()
{
// 判断阻塞队列是否为空
if (bqhead->next == NULL)
return true;
return false;
}
// 按照空闲分区的大小从小到大排序
void sort()
{
// 冒泡排序
struct Unoccupied_block *t = ubhead->next;
int length = 0;
// 获得长度
while (t)
{
length++;
t = t->next;
}
t = ubhead->next;
// 冒泡排序的变形算法
for (int i = 0; i < length - 1; i++)
{
for (int j = 0; j < length - i - 1; j++)
{
struct Unoccupied_block *a, *b, *temp;
temp = (struct Unoccupied_block *)malloc(sizeof(struct Unoccupied_block));
int count = j;
while (count--)
{
t = t->next;
}
a = t;
b = t->next;
if (a->addr_end - a->addr_start > b->addr_end - b->addr_start)
{
temp->addr_start = a->addr_start;
temp->addr_end = a->addr_end;
a->addr_start = b->addr_start;
a->addr_end = b->addr_end;
b->addr_start = temp->addr_start;
b->addr_end = temp->addr_end;
}
t = ubhead->next;
}
}
}
// 按照开始位置排序
void sort2()
{
// 冒泡排序
struct Unoccupied_block *t = ubhead->next;
int length = 0;
// 获得长度
while (t)
{
length++;
t = t->next;
}
t = ubhead->next;
// 冒泡排序的变形算法
for (int i = 0; i < length - 1; i++)
{
for (int j = 0; j < length - i - 1; j++)
{
struct Unoccupied_block *a, *b, *temp;
temp = (struct Unoccupied_block *)malloc(sizeof(struct Unoccupied_block));
int count = j;
while (count--)
{
t = t->next;
}
a = t;
b = t->next;
if (a->addr_start > b->addr_start)
{
temp->addr_start = a->addr_start;
temp->addr_end = a->addr_end;
a->addr_start = b->addr_start;
a->addr_end = b->addr_end;
b->addr_start = temp->addr_start;
b->addr_end = temp->addr_end;
}
t = ubhead->next;
}
}
}
void Output()
{
struct Unoccupied_block *t = ubhead->next;
int count = 0;
printf("------------------------------------\n");
printf("当前的空闲分区链为:\n");
while (t)
{
count++;
printf("%d %d %d\n", count, t->addr_start, t->addr_end);
t = t->next;
}
printf("------------------------------------\n\n");
}
void InsertJob(struct Jobs newJob)
{
// 找到作业队列队尾,插入信息
// !注意需要分配空间,不能直接使用&newJob,因为它是局部变量,跳出该作用域后自动销毁,直接使用导致不可预料的结果
struct Jobs *t1 = jobhead;
while (t1->next)
t1 = t1->next;
struct Jobs *t = (struct Jobs *)malloc(sizeof(struct Jobs));
t->jobID = newJob.jobID;
t->addr_start = newJob.addr_start;
t->addr_end = newJob.addr_end;
t->next = NULL;
t1->next = t;
}
void InsertBQ(struct Blocking_queue newJob)
{
// 找到阻塞队列队尾,插入作业信息
struct Blocking_queue *t = bqhead;
while (t->next)
t = t->next;
struct Blocking_queue *t2 = (struct Blocking_queue *)malloc(sizeof(struct Blocking_queue));
t2->jobID = newJob.jobID;
t2->space = newJob.space;
t2->next = NULL;
t->next = t2;
}
void DeleteJob(int id, int *start, int *end)
{
// 删除作业链表中的项
struct Jobs *t = jobhead;
while (t->next)
{
if (t->next->jobID == id)
break;
t = t->next;
}
// 保存删除作业的信息
*start = t->next->addr_start;
*end = t->next->addr_end;
t->next = t->next->next;
printf("回收成功\n");
printf("作业信息为:ID: %d Space: %d\n", id, *end - *start);
}
struct Blocking_queue *DeleteBQ(int id)
{
// 删除阻塞队列中指定作业号的项,并且返回删除那一项后面的结点
struct Blocking_queue *t = bqhead;
while (t->next)
{
if (t->next->jobID == id)
break;
t = t->next;
}
t->next = t->next->next;
return t->next;
}
bool Arrange(int newJobID, int newJobSpace, bool blockFlag)
{
// 得到三个队列的第一个有效结点
struct Unoccupied_block *head1 = ubhead->next;
struct Jobs *head2 = jobhead->next;
struct Blocking_queue *head3 = bqhead->next;
// 标记是否分配到空间
bool flag = false;
while (head1)
{
// 这个分区大于要求的大小,则取下一部分分配,此时只需修改链表即可
if (head1->addr_end - head1->addr_start > newJobSpace)
{
printf("分配成功\n");
printf("作业信息为:ID: %d Space: %d\n", newJobID, newJobSpace);
struct Jobs newjob;
newjob.addr_start = head1->addr_start;
newjob.addr_end = newjob.addr_start + newJobSpace;
newjob.jobID = newJobID;
newjob.next = NULL;
InsertJob(newjob);
head1->addr_start += newJobSpace;
flag = true;
break;
}
// 若等于,就需要删除这个空闲分区表项
else if (head1->addr_end - head1->addr_start == newJobSpace)
{
printf("分配成功\n");
printf("作业信息为:ID: %d Space: %d\n", newJobID, newJobSpace);
struct Jobs newjob;
newjob.addr_start = head1->addr_start;
newjob.addr_end = head1->addr_end;
newjob.jobID = newJobID;
newjob.next = NULL;
InsertJob(newjob);
head1->previous->next = head1->next;
flag = true;
break;
}
// 否则,寻找下一项
else
head1 = head1->next;
}
if (!flag)
{
printf("分配失败\n");
printf("作业信息为:ID: %d Space: %d\n", newJobID, newJobSpace);
struct Blocking_queue newJob;
newJob.jobID = newJobID;
newJob.space = newJobSpace;
newJob.next = NULL;
// 若处理的本来就是阻塞队列中的作业,且还没有分配成功,就不需要再次进入阻塞队列
if (!blockFlag)
InsertBQ(newJob);
return false;
}
return true;
}
void Free(int newJobID)
{
// 找到三个链表的有效结点
struct Unoccupied_block *head1 = ubhead->next;
struct Jobs *head2 = jobhead->next;
struct Blocking_queue *head3 = bqhead->next;
// 删除指定作业,并从作业链表中得到作业的信息
int jobAddrStart, jobAddrEnd;
DeleteJob(newJobID, &jobAddrStart, &jobAddrEnd);
// 先按照开始位置排序一遍,这样就允许两个算法共用一个回收函数了
sort2();
struct Unoccupied_block *indexInsert = ubhead;
// 寻找插入位置
while (indexInsert)
{
if (head1 == NULL || head1->addr_start >= jobAddrEnd)
{
// 如果删除的作业的结束位置在第一个表项之前,或无表项,那么就要在头结点后插入,直接赋值结束查找
indexInsert = ubhead;
break;
}
else if (indexInsert->addr_end <= jobAddrStart && (indexInsert->next == NULL || indexInsert->next->addr_start >= jobAddrEnd))
break;
else
indexInsert = indexInsert->next;
}
// 前后无邻接,且是个空表
if (indexInsert->next == NULL)
{
struct Unoccupied_block *newItem = (struct Unoccupied_block *)malloc(sizeof(struct Unoccupied_block));
newItem->addr_start = jobAddrStart;
newItem->addr_end = jobAddrEnd;
indexInsert->next = newItem;
newItem->previous = indexInsert;
newItem->next = NULL;
return;
}
// 前后无邻接的情况,直接插入表项
if (indexInsert->addr_end < jobAddrStart && indexInsert->next->addr_start > jobAddrEnd)
{
struct Unoccupied_block *newItem = (struct Unoccupied_block *)malloc(sizeof(struct Unoccupied_block));
newItem->addr_start = jobAddrStart;
newItem->addr_end = jobAddrEnd;
indexInsert->next->previous = newItem;
newItem->next = indexInsert->next;
newItem->previous = indexInsert;
indexInsert->next = newItem;
return;
}
// 前后都邻接
else if (indexInsert->addr_end == jobAddrStart && indexInsert->next->addr_start == jobAddrEnd)
{
indexInsert->addr_end = indexInsert->next->addr_end;
struct Unoccupied_block *t = indexInsert->next;
if (t->next == NULL)
{
indexInsert->next = NULL;
return;
}
t->next->previous = indexInsert;
indexInsert->next = t->next;
return;
}
// 前邻接,修改前面的一项,把它的结束位置修改为释放作业的结束位置
else if (indexInsert->addr_end == jobAddrStart)
{
indexInsert->addr_end = jobAddrEnd;
return;
}
// 后邻接,修改后面的一项,把它的开始位置修改为释放作业的开始位置
else if (indexInsert->next->addr_start == jobAddrEnd)
{
indexInsert->next->addr_start = jobAddrStart;
return;
}
}
void first_fit(bool altype)
{
// 读入文件中的数据,这里假设文件中的数据复合逻辑,比如说,不存在释放还在阻塞队列中作业等非法情况
FILE *fp;
printf("请输入文件名:\n");
char filename[20];
scanf("%s", filename);
if ((fp = fopen(filename, "r")) == NULL)
{
printf("打开文件错误\n");
return;
}
// 初始化三个链表,空闲分区,阻塞队列与已分配空间的作业
jobhead = (struct Jobs *)malloc(sizeof(struct Jobs));
jobhead->next = NULL;
bqhead = (struct Blocking_queue *)malloc(sizeof(struct Blocking_queue));
bqhead->next = NULL;
ubhead = (struct Unoccupied_block *)malloc(sizeof(struct Unoccupied_block));
struct Unoccupied_block *first = (struct Unoccupied_block *)malloc(sizeof(struct Unoccupied_block));
// 这里设置空闲分区表为双向链表
first->addr_start = 0;
first->addr_end = 640;
first->next = NULL;
first->previous = ubhead;
ubhead->next = first;
ubhead->previous = NULL;
ubhead->addr_start = -1;
ubhead->addr_end = -1;
while (!feof(fp))
{
struct Jobs newJob;
int id, type, space;
fscanf(fp, "%d %d %d", &id, &type, &space);
if (type == 1)
{
Arrange(id, space, false);
if (altype)
sort();
Output();
}
else if (type == 0)
{
Free(id);
if (altype)
sort();
// 如果阻塞队列中有未完成分配的作业,取出一项进行分配,直到处理完所有阻塞作业
if (!empty())
{
struct Blocking_queue *t = bqhead->next;
while (t)
{
printf("处理阻塞队列中的作业%d\n", t->jobID);
// 若阻塞队列中的一个作业分配成功,则从阻塞队列中取下这个作业
if (Arrange(t->jobID, t->space, true))
{
if (altype)
sort();
t = DeleteBQ(t->jobID);
continue;
}
t = t->next;
}
}
Output();
}
}
}
int main(void)
{
// 减少代码冗余,因为两个算法只是对内存进行分配回收后是否排序的问题,所以仅用一个函数,用一个标志位区分是否排序
printf("*******************************************************\n\n");
printf("首次适应算法:\n\n");
first_fit(false);
printf("*******************************************************\n\n");
printf("最佳适应算法:\n\n");
first_fit(true);
return 0;
}
- 存储管理的功能:
- 存储分配:根据进程的需求分配内存空间,确保每个进程都有足够的内存资源来执行。
- 存储共享:允许多个进程共享内存中的某些区域,以提高内存的利用率和进程间的通信效率。
- 存储保护:确保每个进程只能访问其被分配的内存区域,防止进程间的非法访问和数据破坏。
- 存储扩充:通过虚拟内存技术,将内存与硬盘等外部存储设备结合,为进程提供比实际物理内存更大的虚拟内存空间。
- 地址映射:将程序中的逻辑地址转换为实际物理地址,以便CPU能够正确地访问内存中的数据。
- 页面置换算法的选择:
- 首次适应算法(First Fit):简单且易于实现,但可能会导致内存碎片问题,增加查找开销。它适用于对内存碎片不敏感,且希望保留大空闲区的场景。
- 最佳适应算法(Best Fit):每次分配都选择最小的满足需求的空闲区,可以提高内存的利用率,但同样会产生内存碎片问题,并且由于需要排序和查找,开销较大。它适用于对内存碎片较为敏感,且希望充分利用每个空闲区的场景。
- 实验中的体会:
- 当使用首次适应算法时,虽然实现简单,但在连续分配和释放内存后,容易产生大量难以利用的小碎片。而使用最佳适应算法时,虽然碎片问题有所缓解,但查找和排序的开销较大。
- 在选择页面置换算法时,需要根据具体的应用场景和需求来权衡各种因素。例如,如果系统对内存碎片较为敏感,且希望充分利用每个空闲区,那么最佳适应算法可能是一个更好的选择。但如果系统对查找开销较为敏感,且希望保持较低的复杂度,那么首次适应算法可能更为合适。