操作系统实验2—实现动态分区分配模拟程序
实验描述
实验内容:
编写一个可变分区存储管理程序,模拟可变分区存储管理方式下对内存空间的动态分配与回收。
实验目的:
内存管理是操作系统中的核心模块,能够合理利用内存,在很大程度上将影响到整个计算机系统的性能。内存的分配和回收与内存管理方式有关。本实验要求学生独立设计并实现可变分区管理方式下的内存分配与回收模拟程序,以加深对各种分配回收算法和可变分区存储管理方式及其实现过程的理解。
实验要求:
1.可以随机输入初始可用内存空间容量,以及进程对内存空间的请求序列,支持首次适应算法、最佳适应算法和最坏适应算法,能够在每次内存分配和回收操作后,显示内存空间的使用情况。具体信息见测试用例格式说明。
2.空闲分区通过空闲区链进行管理,在内存分配时,优先考虑低地址部分的空闲区。
3.当申请空间大于可用空闲内存空间时,不满足此次申请,仍显示此次申请前内存空间的使用情况,后续也不再对此次申请进行任何处理。
4.进程对内存空间的申请和释放可由用户自定义输入。例如,与测试用例示例输入对应的内存空间请求序列为:
(1) 初始状态下可用内存空间为640KB;
(2) 进程1申请130KB;
(3) 进程2申请60KB;
(4) 进程3申请100KB;
(5) 进程2释放60KB;
(6) 进程4申请200KB;
(7) 进程3释放100KB;
(8) 进程1释放130KB;
(9) 进程5申请140KB;
(10) 进程6申请60KB;
(11) 进程7申请50KB;
(12) 进程6释放60KB。
5.分别采用首次适应算法、最佳适应算法和最坏适应算法模拟内存空间的动态分配与回收,每次分配和回收后显示出内存空间的使用情况(参见测试用例示例输出)。
测试用例格式如下:
输入:
动态分区分配算法选择
可用内存空间容量
序号/进程号/申请或释放操作/申请或释放的容量
其中:
(1) 动态分区分配算法:
- 首次适应
- 最佳适应
- 最坏适应
(2) 申请或释放操作:
- 申请操作
- 释放操作
输出:
序号/内存空间状态1/内存空间状态2…
其中,内存空间状态表示分为两种情况:
(1) 内存空间被占用:
内存空间起始地址-内存空间结束地址.1.占用的进程号
(2) 内存空间空闲
内存空间起始地址-内存空间结束地址.0
测试用例示例如下:
测试输入 | 期待的输出 | 时间限制 | 内存限制 | 额外进程 | |
---|---|---|---|---|---|
测试用例 1 | 1 640 1/1/1/130 2/2/1/60 3/3/1/100 4/2/2/60 5/4/1/200 6/3/2/100 7/1/2/130 8/5/1/140 9/6/1/60 10/7/1/50 11/6/2/60 | 1/0-129.1.1/130-639.0 2/0-129.1.1/130-189.1.2/190-639.0 3/0-129.1.1/130-189.1.2/190-289.1.3/290-639.0 4/0-129.1.1/130-189.0/190-289.1.3/290-639.0 5/0-129.1.1/130-189.0/190-289.1.3/290-489.1.4/490-639.0 6/0-129.1.1/130-289.0/290-489.1.4/490-639.0 7/0-289.0/290-489.1.4/490-639.0 8/0-139.1.5/140-289.0/290-489.1.4/490-639.0 9/0-139.1.5/140-199.1.6/200-289.0/290-489.1.4/490-639.0 10/0-139.1.5/140-199.1.6/200-249.1.7/250-289.0/290-489.1.4/490-639.0 11/0-139.1.5/140-199.0/200-249.1.7/250-289.0/290-489.1.4/490-639.0 | 1秒 | 64M | 0 |
测试用例 2 | 2 640 1/1/1/130 2/2/1/60 3/3/1/100 4/2/2/60 5/4/1/200 6/3/2/100 7/1/2/130 8/5/1/140 9/6/1/60 10/7/1/50 11/6/2/60 | 1/0-129.1.1/130-639.0 2/0-129.1.1/130-189.1.2/190-639.0 3/0-129.1.1/130-189.1.2/190-289.1.3/290-639.0 4/0-129.1.1/130-189.0/190-289.1.3/290-639.0 5/0-129.1.1/130-189.0/190-289.1.3/290-489.1.4/490-639.0 6/0-129.1.1/130-289.0/290-489.1.4/490-639.0 7/0-289.0/290-489.1.4/490-639.0 8/0-289.0/290-489.1.4/490-629.1.5/630-639.0 9/0-59.1.6/60-289.0/290-489.1.4/490-629.1.5/630-639.0 10/0-59.1.6/60-109.1.7/110-289.0/290-489.1.4/490-629.1.5/630-639.0 11/0-59.0/60-109.1.7/110-289.0/290-489.1.4/490-629.1.5/630-639.0 | 1秒 | 64M | 0 |
测试用例 3 | 3 640 1/1/1/130 2/2/1/60 3/3/1/100 4/2/2/60 5/4/1/200 6/3/2/100 7/1/2/130 8/5/1/140 9/6/1/60 10/7/1/50 11/6/2/60 | 1/0-129.1.1/130-639.0 2/0-129.1.1/130-189.1.2/190-639.0 3/0-129.1.1/130-189.1.2/190-289.1.3/290-639.0 4/0-129.1.1/130-189.0/190-289.1.3/290-639.0 5/0-129.1.1/130-189.0/190-289.1.3/290-489.1.4/490-639.0 6/0-129.1.1/130-289.0/290-489.1.4/490-639.0 7/0-289.0/290-489.1.4/490-639.0 8/0-139.1.5/140-289.0/290-489.1.4/490-639.0 9/0-139.1.5/140-199.1.6/200-289.0/290-489.1.4/490-639.0 10/0-139.1.5/140-199.1.6/200-289.0/290-489.1.4/490-539.1.7/540-639.0 11/0-139.1.5/140-289.0/290-489.1.4/490-539.1.7/540-639.0 | 1秒 | 64M | 0 |
设计思路
输入的进程序列有序号、进程号、执行操作类型、所需内存四个属性,毫无疑问,用结构体来存储是再合适不过了。用链表来模拟内存的分配,同样也有开始地址、结束地址等不少属性的存储,也是采用的结构体。
struct memory
{
int startaddre; //开始地址
int endaddre; //结束地址
int id; //标记进程号
int size; //分区大小
int state; //是否被占进程用标记 0 表示未被占用,1 表示被占用
struct memory * next;
};
typedef struct node
{
int no; //序号
int id; //进程号
int operation; //执行操作
int volume; //进程所需内存内存
}PCB;
程序概要设计如下图所示:
- main()函数是主程序的入口,控制程序流程,按照输入的调度信号选择相应的算法模块进行运行,并输出相应的结果
- input()函数是输入函数,接受程序输入
- FF()函数是首次适应算法
- BF()函数是最佳适应算法
- WF()函数是最坏适应算法
- FFallocate()函数是 FF 算法的具体实现
- BFallocate()函数是 BF()函数的具体实现
- WFallocate()函数是 WF()函数的具体实现
- free_()函数的作用是释放分区
- output()函数是输出函数,输出分区链表状态
int main() //主函数入口
void FF(memory *p); //首次适应
void BF(memory *p,memory *head);//最佳适应
void WF(memory *p,memory *head);//最坏适应
void FFallocate(PCB pc,memory *p); //最先适应分配分区算法
void BFallocate(PCB pc,memory *p,memory *head) ;//最佳适应
void WFallocate(PCB pc,memory *p,memory *head) ;//最坏适应
void free_(PCB pc,memory *p); //释放分区算法
void input() ; //输入进程序列
void output(PCB pc,memory *p) ; //输出分区链表状态函数
上机代码
代码使用 C++ 语言进行编写
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<string>
using namespace std;
struct memory
{
int startaddre;//开始地址
int endaddre;//结束地址
int id;//标记进程号
int size;//分区大小
int state;//是否被占进程用标记 0 表示未被占用,1 表示被占用
struct memory * next;
};
typedef struct node
{
int no;//序号
int id;//进程号
int operation;//执行操作
int volume;//进程所需内存内存
}PCB;
PCB pcb[1010];//输入进程数组
int sig=0;//算法标志
int num=0;//输入进程大小
int total;//内存容量
PCB pc;
void FF(memory *p);//首次适应
void BF(memory *p,memory *head);//最佳适应
void WF(memory *p,memory *head);//最坏适应
void FFallocate(PCB pc,memory *p) ;//最先适应分配分区算法
void BFallocate(PCB pc,memory *p,memory *head) ;//最佳适应
void WFallocate(PCB pc,memory *p,memory *head) ;//最坏适应
void free_(PCB pc,memory *p);//释放分区算法
void input();//输入进程序列
void output(PCB pc,memory *p) ;//输出分区链表状态函数
int main()
{
//程序输入
input();
memory *head,*p;
head = (memory*)malloc(sizeof(memory));//初始化
head->next = NULL;
head->size = total;
head->startaddre = 0;
head->endaddre = head->size + head->startaddre - 1;
head->state = 0;
p = head;
//选择算法
switch (sig)
{
case 1:FF(p);break;
case 2:BF(p,head);break;
case 3:WF(p,head);break;
}
return 0;
}
void input()
{
// freopen("osin.txt", "r", stdin);
// freopen("osout.txt", "w", stdout);
sig = 0;
cin >> sig;//算法选择标志
total = 0;
cin >> total;//输入可用容量
num = 0;//输入进程序列
while (~scanf("%d/%d/%d/%d", &pcb[num].no, &pcb[num].id, &pcb[num].operation, &pcb[num].volume))
{
num++;
}
}
void output(PCB pc,memory *p)//输出分配区状态函数
{
printf("%d",pc.no);
while(p!=NULL)
{
if(p->next!=NULL)//不是最后一个链表区
{
if(p->state==1) //内存被占用
printf("/%d-%d.%d.%d",p->startaddre,p->endaddre,p->state,p->id);
else
printf("/%d-%d.%d",p->startaddre,p->endaddre,p->state);
}
else //最后一个链表区 ,换行
{
if(p->state==1)
printf("/%d-%d.%d.%d\n",p->startaddre,p->endaddre,p->state,p->id);
else
printf("/%d-%d.%d\n",p->startaddre,p->endaddre,p->state);
}
p=p->next;
}
}
void FFallocate(PCB pc,memory *p) //首适应分配内存算法
{
while(p!=NULL) //从链表首指针一直找到尾指针
{
if((pc.volume<p->size)&&p->state==0)
//进程未分配且能在分区分配这个进程 ,
//将有剩余内存空间 ,相当于在后面插入节点
{
p->id=pc.id;//分区记录下进程号
p->state=1;//标记被占用
p->endaddre=p->startaddre+pc.volume-1;
memory *add;//增加一个链表
add=(memory *)malloc(sizeof(memory));
//在p后面插入add链表
add->startaddre=p->endaddre+1;
add->size =p->size-pc.volume;
add->endaddre=add->startaddre+add->size-1;
add->state=0;
add->next=p->next ;//插入
p->next=add; //操作
p->size=pc.volume;
break;
}
//进程未分配且能在分区分配这个进程 ,
//没有剩余内存空间 ,只是占用标记位改变
else if(pc.volume==p->size&&p->state==0)
{
p->id=pc.id;//分区记录下进程号
p->state=1;//标记被占用
break;
}
p=p->next;
}
}
void BFallocate(PCB pc,memory *p,memory *head)
{
int sub=-1;//分区链表内存与将要分配进程内存之差
int minsub=-1;//分区链表内存与将要分配进程内存之差最小值。
//初始化为-1;
int flag1=0;//标记是否是第一次适应
int location=0; //记录最小之差的链表位置
int len=0;//记录链表位置
while(p!=NULL)
{
if(p->state==0&&(p->size-pc.volume)>=0) //链表区空闲且内存比进程所需内存大则可以存进程
{
//不能把sub在if外面赋值,否则sub将一直>=0
sub=p->size-pc.volume;//链表区与进程所需内存之差
if(flag1==0)//第一个能存进程的链表
{
minsub=sub;//此时内存之差
location=len; //记录链表位置
}
else //从多个链表找出一个内存之差最小的
{
if(sub<minsub)
{
minsub=sub;
location=len;//记录其下标
}
}
flag1++;
}
len++;//表示链表位置后移
p=p->next;
}
if(minsub>=0) /*/能存入进程minsub才>=0 /*/
{
int i;
p=head;
//之前p指向链表尾指针,现在应该指向头指针
//从头开始寻找标志位 ,满足则 p指向它
for(i=0;i<location;i++)
{
p=p->next;//p指向能放入进程且两者内存之差最小的链表
}
if(minsub>0)
{
p->id=pc.id;//分区记录下进程号
p->state=1;//标记被占用
p->endaddre=p->startaddre+pc.volume-1;//尾地址为首地址加上进程内存大小-1
memory *add;//增加一个链表
add=(memory *)malloc(sizeof(memory));
//在p后面插入add链表
add->startaddre=p->endaddre+1;
add->size =p->size-pc.volume;
add->endaddre=add->startaddre+add->size-1;
add->state=0;//表示未被占用
add->next=p->next ;
p->next=add;
p->size=pc.volume;
}
else
{
p->id=pc.id;//分区记录下进程号
p->state=1;//标记被占用
}
}
}
void WFallocate(PCB pc,memory *p,memory *head)
{
int sub=-1;//分区链表内存与将要分配进程内存之差
int maxsub=-1;//分区链表内存与将要分配进程内存之差最大值,
//初始化为-1
int flag1=0;//标记是否是第一次适应
int location=0; //记录最小之差的链表位置
int len=0;//记录链表位置
while(p!=NULL)
{
if(p->state==0&&(p->size-pc.volume)>=0) //链表区空闲且内存比进程所需内存大则可以存进程
{
sub=p->size-pc.volume;//链表区与进程所需内存之差
if(flag1==0)//第一个能存进程的链表
{
maxsub=sub;//此时内存之差
location=len; //记录链表位置
}
else //从多个链表找出一个内存之差最小的
{
if(sub>maxsub)
{
maxsub=sub;
location=len;//记录其下标
}
}
flag1++;
}
len++;//表示链表位置后移
p=p->next;
}
if(maxsub>=0) /*/能存入进程maxsub才>=0 /*/
{
int i;
p=head;
//之前p指向链表尾指针,现在应该指向头指针
//从头开始寻找标志位 ,满足则 p指向它
for(i=0;i<location;i++)
{
p=p->next;//p指向能放入进程且两者内存之差最小的链表
}
if(maxsub>0)
{
p->id=pc.id;//分区记录下进程号
p->state=1;//标记被占用
p->endaddre=p->startaddre+pc.volume-1;//尾地址为首地址加上进程内存大小-1
memory *add;//增加一个链表
add=(memory *)malloc(sizeof(memory));
//在p后面插入add链表
add->startaddre=p->endaddre+1;
add->size =p->size-pc.volume;
add->endaddre=add->startaddre+add->size-1;
add->state=0;//表示未被占用
add->next=p->next ;
p->next=add;
p->size=pc.volume;
}
else
{
p->id=pc.id;//分区记录下进程号
p->state=1;//标记被占用
}
}
}
void free_(PCB pc,memory *p)
{
int count=0;
memory *pp,*pnext;//链表长度大于1时,pp是p上一个链表
while(p!=NULL)
{
if(p->id==pc.id) //遍历找到将要释放的进程位置
{
if(count==0) //释放的分配区在链表首部
{
if(p->next!=NULL)
{
pnext=p->next;
if(pnext->state==0)
{
p->state=0;//标记进程未被占用
//删除q节点,p节点大小和末地址增加
p->next=pnext->next;
p->size+=pnext->size;
p->endaddre=pnext->endaddre;
free(pnext);
break;
}
else
{
p->state=0;
break;
}
}
else
{
p->state=0;//标记进程未被占用
break;
}
}
else if(p->next!=NULL)//释放的分配区在链表中间
{
pnext=p->next;
if(pp->state==1&&pnext->state==1)//前后链表区都有进程占用
{
p->state=0;//只把进程占用标记置为 0
break;
}
//只是后面链表区被占用
//合并内存,删除p指向节点
else if(pp->state==0&&pnext->state==1)
{
pp->next=p->next;
pp->size+=p->size;
pp->endaddre=p->endaddre;
free(p);
break;
}
//只是前面链表区被占用
//合并内存,删除p指向节点
else if(pp->state==1&&pnext->state==0)
{
pp->next=p->next;
pnext->size+=p->size;
pnext->startaddre=p->startaddre;
free(p);
break;
}
//前后链表区都不被占用
//合并内存,删除p指向节点和p后面一个节点
else if(pp->state==0&&pnext->state==0)
{
pp->next=pnext->next;
pp->size+=p->size+pnext->size;
pp->endaddre=pnext->endaddre;
free(p);
free(pnext);
break;
}
}
else if(p->next==NULL)//释放的分配区在链表尾
{
if(pp->state==1)//前一个链表区不为空
{
p->state=0;
break;
}
else //前一个链表区为空
{
pp->next=p->next;
pp->state=0;
pp->size+=p->size;
pp->endaddre=p->startaddre;
free(p);
break;
}
}
}
pp=p; //pp指向下次遍历的p指向的前一个节点
p=p->next;
count++;
}
}
void FF(memory *p)//首次适应
{
int i=0;
for(i=0;i<num;i++)//是否每个进程分配或释放完
{
if(pcb[i].operation==1)
{
FFallocate(pcb[i],p);
output(pcb[i],p) ;
}
else //(pcb[i].operation==2)
{
free_(pcb[i],p);
output(pcb[i],p);
}
}
}
void BF(memory *p,memory *head) //最佳适应
{
int i=0;
for(i=0;i<num;i++)//是否每个进程分配或释放完
{
if(pcb[i].operation==1)
{
BFallocate(pcb[i],p,head);
output(pcb[i],p) ;
}
else //(pcb[i].operation==2)
{
free_(pcb[i],p);
output(pcb[i],p);
}
}
}
void WF(memory *p,memory *head) //最坏适应
{
int i=0;
for(i=0;i<num;i++)//是否每个进程分配或释放完
{
if(pcb[i].operation==1)
{
WFallocate(pcb[i],p,head);
output(pcb[i],p) ;
}
else //(pcb[i].operation==2)
{
free_(pcb[i],p);
output(pcb[i],p);
}
}
}
测试结果
程序采用黑盒测试的方式,提交到 OJ 系统上进行在线评测
可以看到,OJ 的测试用例全部通过
心得体会
通过本次实验,上机代码模拟实现了三种动态分区分配算法,对操作系统内部的空闲分区分配方式有了更深刻的认识和感受。FF 首次适应算法在低址部分不断被划分,会留下很多难以利用的、很小的空闲碎片。BF 最佳适应算法似乎是最优的,但是可能会在存储器中留下许多难以利用的碎片。WF 最坏适应算法与最佳适应算法刚好相反,它会导致存储器中缺乏大的空闲分区,但是未必是最坏的,它产生碎片的可能性最小。