内存管理、分配与回收模拟实验
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
华中师范大学计算机科学系
实验报告书
实验题目:内存管理、分配与回收
课程名称:操作系统
主讲教师:辅导教师:课程编号:班级:实验时间:
一、实验目的:
(1)掌握内存分区管理的基本思想,理解内存分配表。
(2)深入理解可变分区的内存分配策略,掌握首先适应算法、最佳适应算法和最坏适应算法。
(3)掌握内存碎片产生的途径以及解决碎片的方法——拼接技术。
(4)实现分区的回收。
针对内存管理的相关活动,研究内存空闲队列的动态组织与管理问题,以及在此基础上执行的内存分配与回收活动。
二、实验内容:
本实验将利用伙伴系统来组织内存空闲块队列和已使用内存块队列。
从初始化快照、某一组作业申请内存块前的快照、分配成功后的快照等状态出发,结合内存分配原语(算法)和内存回收原语(算法)的实现,结合实际内存块的动态分配与回收情况(某一快照),研究内存空闲块队列的组织、变化及其队列管理方面的问题。
具体内容如下:
(1)实现内存分配算法和内存回收算法。
(2)以伙伴系统的组织方式管理内存空闲队列和已使用内存块队列,具体的组织策略应分别考虑首次适应策略、最佳适应策略和最坏适应策略。
(3)考虑在某一内存使用一段时间的快照,给出一组作业的内存申请,判断该申请是否可以被满足。
三、实验要求
(1)分配算法中切割空闲区是从低地址开始;
(2)需考虑门限值情况,门限值是指切割空闲区后剩下的区域若小于一个用户给定的值时,就不切割该空闲区,统统分给申请者,这个值由用户指定;
(3)回收算法需要考虑上邻、下邻、上下邻和不相邻四种情况。
四、实验环境:
实践平台:
编写环境:
编译器:
五、实验设计原理
(1)可变分区基本思想
可变分区是指系统不预先划分固定分区,而是在装入程序时划分,使程序分配的大小正好等于程序的需求量,且分区的个
数是可变的,这样有较大的灵活性,较之固定分区能获得更好的内存利用率。
其状态如图所示:
(2)内存分配表
内存分配表由两张表格组成:一张是已分配表,记录已装入的程序在内存中占用分区的起始地址和长度,并表之位指出占用分区的程序名;另一张是空闲区表,记录内存中可供分配的空闲区的起始地址和长度,用标志位指出该分区是未分配的空闲区。
其基本结构如图所示:
(3)分配策略
①首次适应算法():从空闲分区表的第一个表目起查找
该表,把最先能够满足要求的空闲区分配给作业,这种方法的目的在于减少查找时间。
为适应这种算法,空闲分区表(空闲区链)中的空闲分区要按地址由低到高进行排序。
该算法优先使用低址部分空闲区,在低址空间造成许多小的空闲区,在高地址空间保留大的空闲区。
②最佳适应算法( ):从全部空闲区中找出能满足作业要求的、且大小最小的空闲分区,这种方法能使碎片尽量小。
为适应此算法,空闲分区表(空闲区链)中的空闲分区要按从小到大进行排序,自表头开始查找到第一个满足要求的自由分区分配。
该算法保留大的空闲区,但造成许多小的空闲区。
③最差适应算法( ):从全部空闲区中找出能满足作业要求的、且大小最大的空闲分区,从而使链表中的结点大小趋于均匀,适用于请求分配的内存大小范围较窄的系统。
为适应此算法,空闲分区表(空闲区链)中的空闲分区按大小从大到小进行排序,自表头开始查找到第一个满足要求的自由分区分配。
该算法保留小的空闲区,尽量减少小的碎片产生。
(4)碎片拼接
采用可变分区存储管理方案后,经过一段时间的分配回收,内存中会存在很多很小的空闲块。
需要在适当时刻进行碎片整理,通过内存中移动程序,把所有空闲碎片合并成一个连续的大空闲区且放在内存的一端,而把所有程序占用区放在内存另一端。
(5)分区的回收
当用户执行结束后,系统要收回已经使用完毕的分区,将其记录在空闲区表中。
一般情况下应考虑四种情况:
1.回收分区的上邻分区是空闲的,需要将这两个相邻的空闲分区合并成一个更大的空闲区,然后修改空闲区表。
2.回收分区的下邻分区是空闲的,需要将这两个相邻的空闲分区合并成一个更大的空闲区,然后修改空闲区表。
3.回收分区上下邻区都是空闲的,需要将三者合并成一个更大的空闲区,然后修改空闲表。
4.回收区的上邻下邻区都不是空闲的,则直接将空闲区记录在空闲表中。
五、实验详细实现过程与算法流程
(一)数据结构
1. 作业结构
本实验的重点是对内存的管理、分配与回收,所以作业的内部结构简单,仅包含:进程名()、程序大小()和内部指针(),其结构如图:
2.分配表结构
本实验的分配表结构包含:进程名兼区名()、地址()、程序大小()、状态()和内部指针(),其结构如图:
2.队列结构
作业队列()、占用表队列()和空闲表队列()。
其结构如图所示:
(二)程序结构思维导图
六、实验测试与过程分析
(1)实验数据
内存大小设置为1024k。
进行操作后,由计算机随机生成一个作业,每生成一个作业,作业名累次加1,作业大小随机设置位200500k大小,其结构如图所示:
(2)测试首先适应算法()
①创建5个作业,作业队列、空闲表队列、占用队列如图所示:
②分配内存直至内存不足:
③依次终止2、 1、3
对算法进行了多组测试,仅记录了一组测试结果。
通过多组测试的结果分析,每次划分内存空间,均是从低地址处开始划分,在结束作业,收回内存时,根据相邻情况与否,进行合并后在按地址升序插入到空闲队列中。
(3)最佳适应算法()
①创建5个作业,作业队列、空闲表队列、占用队列如图所
示:
②分配内存直至不足:
③终止作业2、1:
对算法进行了多组测试,仅记录了一组测试结果。
通过多组测试的结果分析,每次划分内存空间,均是从低地址处开始划分,在结束作业,收回内存时,根据相邻情况与否,进行合并后按照空闲块的大小升序插入空闲队列中。
(4)最坏适应算法()
①创建5个作业,作业队列、空闲表队列、占用队列如图所
示:
②分配内存直至不足:
③终止作业2、1:
对算法进行了多组测试,仅记录了一组测试结果。
通过多组测试的结果分析,每次划分内存空间,均是从低地址处开始划分,在结束作业,收回内存时,根据相邻情况与否,进行合并
后按照空闲块的大小降序插入空闲队列中。
六、实验结果分析(问题的发现、分析、解决方案与创新)
1. 在设计分配表数据结构的时候,起初考虑了顺序表结构,发现在执行整个内存的分配与回收的过程中,需要较多地进行删除、插入操作,顺序表并不合适,遂改为链表结构,实现相关的链表操作。
2. 因为设计了链表的数据结构,对不同算法中占用表和空闲表中块的排序极为困难,换一种思路,便是在回收合并的时候,如果有相邻的空闲块,把它们摘取下来,进行合并,把合并后的结果再插入到空闲表中,这样的方式与之前想过的在空闲表原有的块上合并相邻空闲块更为简单,容易实现,十分地方便。
3. 在考虑到浏览结果的方便性和直观性,通过相应的格式控制,以表格的形式展现各个队列的状态,各个作业在何种操作后进入何种队列有了清晰的直观的追踪,充分地展示内存分配过程。
七、源程序(加注释)
见附页
八、实验体会与改进意见
(1)实验体会
1.本次实验主要是熟悉内存管理的相关概念及内存管理的各部分内容,并明确内存管理中所运用到的数据结构、控制机制的基本原理,代码实现了三种内存分配策略,分区的回收,代码实现了实验所需要的各种队列的部分基本操作,实现代码复用。
2.通过编程自行模拟了操作系统的内存分配与回收,充分理解了分配表的重要作用,更深入了其中的逻辑过程,而且对自己的编程能力也有所提高。
(2)改进意见
1.由于一些其他原因,本实验中对碎片整合问题没有实现。
现主要描述一下实现思路:可设置一个函数来处理碎片整合问题,设置一个指针,从占用队列()中依次取出空闲块加入到指针后,形成新的占用队列。
对于空闲队列删除原有队列块,新的空闲队列只有一个空闲块,其地址为占用队列最后一个占用块的。
2.本实验中计算机生成的作业大小范围为200500k,内存区大小为1024k,则会出现,则大部分可能出现内存区只能存放2到3个的作业,可以适当的调小作业大小范围或者调大内存大小,从而观察多个作业的内存管理、分配和回收的情况,更细致地对内存管理进行追踪。
附页:实验三<>
<>
<>
1024 100
1
作业结构 {
; 进程名 ; 大小 *; 指针
};
分配结构
{
; 进程名 ; 地址
; 大小 ; 状态
*; 指针
};
n;
队列结构
{
*;
*;
;
};
作业队列
;
队列结构
{
*;
*;
;
};
占用、空闲队列
, ;
(){
("");
("\t \n");
("\ |\n"); ("\ |\n");
("\ |\n"); ("\ [1] |\n");
("\ |\n"); ("\ [2] |\n");
("\ |\n");
("\ [3] |\n");
("\ |\n"); ("\ [0] |\n");
("\ |\n"); ("\\n");
}
(){
("");
("\t \n");
("\ |\n"); ("\ |\n");
("\ |\n"); ("\ [1] |\n");
("\ |\n"); ("\ [2] |\n");
("\ |\n"); ("\ [3] |\n");
("\ |\n"); ("\ [0] |\n");
("\ |\n"); ("\\n");
}
创建作业插入作业队列尾 (){
*p;
p = ();
> = ;
> = () % 300 + 200; > = ;
( )
{
= p;
= p;
;
}
{
> = p;
= p;
;
}
}
取出作业队列队首元素 *(){
*q;
( 0)
(" !\n"); {
q = ;
= >;
;
}
q;
}
打印作业队列
(){
*p;
p = ;
("\n\ :\n");
( 0){
("\ !\n"); }{
("\\n");
("\\\\n");
("\\n");
(p ){
("\\\\n", >, >); p = >;
}
("\\n");
}
}
打印空闲表
(){
*p;
p = >;
("\n\ :\n");
( 0){
("\ !\n");
}
{
("\\n");
("\\\\n");
("\\n");
(p ){
("\\\\\n", >, >, >); p = >;
}
("\\n");
}
}
打印占用表
(){
*p;
p = >;
("\n\ :\n");
( 0)
("\ !\n");
{
("\\n");
("\\\\n");
("\\n");
(p ){
("\\\\\n", >, >, >); p = >;
("\\n");
}
}
初始化各队列
(){
初始空闲块
*q = ();
> = 0;
> = 0;
> = ;
> = ;
> = ;
初始空闲块入空闲队列 = ();
> = q;
= q;
;
初始化占用队列
= ();
> = ;
= 0;
}
加入到占用队列
( *){
*q = >, *s = ;
如果空,加入到队首
(q ){
> = ;
;
;
}
按地址升序排列占用队列 (q ){
(> < >){
> = q;
> = ;
;
;
}
s = q;
q = >;
}
如果地址大于前面元素,则加入到队列尾 (q ){
> = ;
;
}
}
( *, i){
*q = >, *s = ;
如果空,加入到队首
(q ){
> = ;
;
;
}
(q ){
首先适应算法插入空闲队列中
(> < > i 1){
> = q;
> = ;
;
;
}
最佳适应算法插入空闲队列中 (> < > i 2){
> = q;
> = ;
;
;
}
最坏适应算法插入空闲队列中 (> > > i 3){
> = q;
> = ;
;
;
}
{
s = q;
q = >;
}
}
插入队尾
(q ){
> = ;
;
;
}
}
按相应算法分配内存
( i){
*q = >, *s = ;
*p = ();
空闲队列空,内存不足 (q ){
("\n\ !\n");
;
}
从作业队列中取出作业 * = ();
(q ){
(> >= >){
如果分配后剩余大小不足100k就全部分配给该作业
(> - > < 100){
> = >;
;
> = >;
> = ;
> = ;
(q);
;
}
否则进行切割
{
> = >;
> = >;
> = >;
> = ;
> = ;
占用的内存进占用表
(p);
> = 0;
> = > + >;
> = > - >;
> = ;
> = >;
;
剩下的空闲内存重新按算法插入空闲表 (q, i);
;
}
}
s = q;
q = >;
}
没有满足大小的空闲块则内存不足,
将取出的作业重新加入到作业队列队首
(q ){
("\n\ !\n");
> = ;
= ;
;
}
从占用队列中取出占用块 *( ){
*q = >, *s = ;
( 0)
{
("\n\ !\n"); ;
}
(q ){
(> ){
> = >;
> = 0;
> = ;
> = ;
;
q;
}
s = q;
q = >;
(q )
("\n\ !\n");
;
}
空闲块合并
*( *){
*q = >, *s = ;
*x, *y;
* = ();
如果空闲表为空,无需合并直接返回空闲快 (q ){
;
}
(q ){
指向可以合并的单元
(> + > > > + > >){
指向可合并单元
邻下找邻上
(> + > >){
x = q;
> = >; q = >; ;。