赞
踩
预备内容:阅读操作系统的内存管理章节内容,理解有关虚拟存储器、段式存储管理等概念,并掌握段式管理内存的分配和回收过程。
实践准备:掌握一种计算机高级语言的使用。
要求完成的主要任务: (包括课程设计工作量及其技术要求,以及说明书撰写等具体要求)
采用段式管理方案实施内存分配和回收。能够处理以下的情形:
⑴ 在完成指定分配算法基础上还可以选择其它分配算法进行模拟设计;
⑵ 能够输入给定的内存大小,进程的个数,每个进程的段数及段大小;
⑶ 当某进程提出申请空间的大小后,显示能否满足申请,以及为该进程分配资源后有关内存空间使用的情况;
⑷ 显示回收后内存空间的使用情况(注意回收后的合并)。
⑴ 目的、功能与要求(明确该选题的作用并列出所选功能及要求);
⑵ 问题的详细描述、需求分析(分析说明相关算法原理及具体的实验内容);
⑶ 数据结构、功能设计(给出功能结构图、处理流程图);
⑷ 开发平台及源程序的主要部分(对主要代码段附文字注释);
⑸ 测试用例,运行结果与运行情况分析;
⑹ 自我评价与总结:
你认为你完成的设计哪些地方做得比较好或比较出色;
ii)什么地方做得不太好,以后如何改正;
iii)从本设计得到的收获(在编写,调试,执行过程中的经验和教训);
iv)完成本题是否有其他方法(如果有,简要说明该方法);
对实验题的评价和改进意见,请你推荐设计题目。
设计安排一周:周 1、周 2:完成程序分析及设计。
周 2、周 3:完成程序调试及测试。
周 4、周 5:验收、撰写课程设计报告。
(注意事项:严禁抄袭,一旦发现,一律按 0 分记)
1 目的、功能与要求
学习操作系统的内存管理章节内容,理解有关虚拟存储器、段式存储管理等概念,并掌握段式管理内存的分配和回收过程,并用一种计算机高级语言实现段式管理内存的分配和回收程序,模拟内存分配和回收的过程。
课程设计是对学生的一种全面综合训练,是与课堂听讲、自学和练习相辅相成的必不可少的一个教学环节。操作系统这门课程安排的课程设计的目的是旨在要求我们进一步巩固课堂上所学的理论知识,深化理解和灵活掌握教学内容,选择合适的数据逻辑结构表示问题,然后编制算法和程序完成设计要求,从而进一步培养我们独立思考问题、分析问题、解决实际问题的动手能力。
段式管理存储管理的分配与回收的功能是:以段为单位分配内存,每个段定义一组逻辑上完整的程序或数据,段号之间无顺序关系,段长不固定,每段分配一个连续的内存区,同一进程所包含的各段之间不要求连续。只把那些经常访问的段驻留内存,而把那些将来一段实践内不被访问的段放入外存,等需要时再调入内存。
段式内存分配与释放在进程的执行过程中动态进行,段式存储管理可以实现:在有足够空闲区时进行分配:可采用动态分区管理的分配算法(最先/最佳/最坏适应法),当没有足够空闲区时淘汰内存中一个或几个段:可采用动态页式管理淘汰算法(LRU 及其近似算法);内存的回收可采用动态分区管理的内存回收方法(内存拼接)。
在采用段式管理方案实施内存分配和回收。能够处理以下的情形:
在完成指定分配算法基础上还可以选择其他分配算法进行模拟设计;
能够输入给定的内存大小,进程的个数,每个进程的段数以及段大小;
当某进程提出申请空间的大小后,显示能否满足申请,以及为该进程分配资源后的相关内存空间使用的情况;
显示回收后内存空间的使用情况(注意回收后的合并)
问题的详细描述、需求分析
问题的详细描述
段式存储管理在内存分配时可能面对两种情况:
当有足够的空闲区进行分配时,可选择动态分区管理的最先、最佳或最坏适应法。
最先适应:分配首个足够大的物理块。将空闲区按地址顺序排列,每次分配时从头开始查找,一旦找到足够大的空闲孔,就可以停止。
最优适应:分配最小的足够大的物理块。将空闲区按空闲区块的大小顺序排列(从小到大),每次分配时从头开始查找足够大的空闲孔。
最坏适应:分配最大的物理块。将空闲区按空闲区块的大小顺序排列(从大到小),每次分配时从头开始查找。这种方法可以产生最大剩余空。
在没有足够空闲区大时,可以先进行合并空闲区(内存紧缩)以形成长度不小于 X 段长的空闲区,若还不够大,就可采用动态页式管理的淘汰算法(LRU 及其近似算法)。
本次实验中采用最不经常使用页面淘汰算法 LFU,当需要某一页时,首先淘汰到当前时间为止,被访问次数最少的那一页。实现方法时给页表的每一页增设一个访问计数器。当该页被访问时,访问计数器加 1。发生缺页中断时,淘汰计数值最小的那一页,并将其计数器清 0。
段式存储管理是基于为用户提供一个方便灵活的程序设计环境而提出来的。段式管理的基本思想是:把程序按内容或过程(函数)关系分成段,每段有自己的名字。一个用户作业或进程所包含的段对应于一个二维线性虚拟空间,也就是一个二维虚拟存储器。
段式管理程序以段为单位分配内存,和页式管理时一样,段式管理也采用只把那些经常访问的段驻留内存,而把那些在将来一段时间内不被访问的段放入外存。它允许只装入部分页面的程序和数据,便启动运行。以后,在通过调段功能和段置换功能,陆续把即将要运行的段调入内存,同时把暂时不运行的段换出到外存上,置换时以段为单位。实现将程序正在运行时所需的但尚未在内存的 段调入内存,再将内存中暂时不用的段从内存置换到外存磁盘上。
为了实现段式存储管理,段表应增加相应的内容,放映该段是否在内存,在内存的位置,被访问的次数
段号 | 开始地址 | 段长 | 状态位 | 访问位 |
---|---|---|---|---|
各字段说明如下:
段号:与段名一一对应
开始地址:该段在内存或外存的起始气质
段长:该段在内存或外存的实际长度
状态位:该段是在内存或外存
访问位:该段是在内存或外存(为淘汰算法而设)
数据结构、功能设计
数据结构
模拟设计时,用一个分区说明表表示内存的占用、空闲情况,每一项有占用一段内存的进程名,段号,段长,起始地址等;用一个空闲表来表示内存中的空闲区,空闲表的每一项对应分区说明表中的一项,空闲区号,空闲区长度等都从分区说明表中得到。
当模拟进程的段的内存分配时需要一个请求表,分配时从请求表中选择一个进程的某一段来分配内存。
//请求表
struct Request {
string processName;//请求进程名
int segnum;//哪个进程的哪个段
int length;//请求进程请求段的长度
};
功能设计
功能结构图
本次课程设计程序功能如下:
图 3.1 功能结构图
程序流程图
段式存储管理程序模拟首先从文件中读入已写入文件中的进程、进程名和每个进程的段数,段长等信息,并存入相关的进程表,段表数据结构,接着就可以对进程的段进行内存的分配,内存的分配可以按照最先适应发、最优适应法或最坏适应法进行段的分配与回收。如果出现内存中的空闲块都小于将要分配段的大小时,可以先进行内存紧缩,将各占用内存分区向内存的一段移动,使各空闲分区聚集在另一端,然后将各个空闲分区合并成一个空闲分区;在进行分配,若空闲区仍然不够,就需要采用动态页式管理的淘汰算法(FIFO 淘汰算法、LRU 及其近似算法)去淘汰内存中的一个或几个段。
分配后若要释放,则采用动态分区管理的内存回收方法(内存拼接)
算法流程图如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jyxq1aOJ-1654590502679)(https://www.writebug.com/myres/static/uploads/2022/6/7/5f244a8347c221e833911378b48b5df8.writebug)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9x7BsSFG-1654590502680)(https://www.writebug.com/myres/static/uploads/2022/6/7/2aaa5829838bddc1a8d43ceb2b65ea1a.writebug)]
开发平台及源程序的主要部分
开发平台
系统:windows10
语言:C++
开发工具:Visual Studio 2017
源程序的主要部分
段式存储管理的段分配内存主要部分:
//最坏分配算法 void worst(string processName, int segnum) { //先对可用表进行排序 sort(available.begin(), available.end(), cmpDec); //从请求表中找到相应的 进程相关信息 Request allocatReq;//要分配的请求进程的段 int del;//记住要分配的进程在请求表中的位置,分配后删除请求要用 for (int i = 0; i < request.size(); i++) { if (request[i].processName == processName && request[i].segnum == segnum) { allocatReq = request[i];//保存这个要分配的段的信息 del = i;//要分配的段在请求表中的位置??? break; } } bool flag; //通过查找空闲表来放置段 flag = worstmain(processName, allocatReq); //如果当前空闲表中的空闲区大小都小于请求的段的大小 if (flag == false) {//如果不够大 compaction();//内存紧缩技术,先进行紧缩 flag = worstmain(processName, allocatReq); //分配 if (flag == false) {//如果还不够大,进行淘汰 eliminate(allocatReq.length);//淘汰不小于,要分配段长度的若干个段 淘汰后的设置为NOUSE flag = worstmain(processName, allocatReq);//淘汰后继续分配 } } if (flag) {//若成功,将分配的进程从请求表中删除,前面保存了进程在请求表中的位置 //分配成功后 修改段表,释放后的进程段修改段表 temp = head; while (temp) { if(temp->processName == processName && temp->segnum == segnum) { processTable[processName].segTable[segnum].startAdd =temp->address;//物理初始地址 processTable[processName].segTable[segnum].state = 1;//在内存中 processTable[processName].segTable[segnum].visitCount++;//访问次数加一 break; } temp=temp->next; } request.erase(request.begin() + del); printf("成功分配!\n"); } else { printf("分配失败!\n"); } }
最坏适应算法主要部分:
bool worstmain(string processName,Request allocatReq) { //比较可用表与要分配进程内存的大小 bool flag = false; int num = 0; for (int i = 0; i < available.size(); i++) { //如果可以放得下,就要更新分区说明表和可用表 if (allocatReq.length < available[i].length) { //分区说明表要分割已有结点 temp = head; Partition *newNode = new Partition; if (temp == nullptr) {//分区说明表为空 createhead(processName, allocatReq); } else {//若分区说明链表不为空 while (temp) { temp->itemCode = num; //分区说明表区号 if (temp->itemCode == available[i].itemCode) { temp->length = allocatReq.length;//新加的进程所需内存大小 temp->processName = processName;//新进程的名称 temp->segnum = allocatReq.segnum;//进程的段号 temp->state = USEFUL;//占用 newNode->address = temp->address + temp->length; newNode->itemCode = temp->itemCode + 1; //num = newNode->itemCode; newNode->length = available[i].length - temp->length;//进程添加后 这一块所剩内存大小 可用表相关项-新进程的大小 newNode->processName = " "; newNode->segnum = -1;// newNode->state = NOUSE; //添加链表结点 newNode->next = temp->next; temp->next = newNode; } num++; temp = temp->next; } } //更新可用表即可 updateAvail(); flag = true; break; } else if (allocatReq.length == available[i].length) {//段长正好等于空闲区长度 //分区说明表要改相应的内容就行了 temp = head; //如果分区说明表为空 头结点为空 if (temp == nullptr) { //头结点为空 分配进行了 也不用加新的结点 createhead(processName, allocatReq); } else { while (temp) { if (temp->itemCode == available[i].itemCode) { temp->length = allocatReq.length;//新加的进程的段所需内存大小 temp->processName = processName;//新进程的名称 temp->segnum = allocatReq.segnum;//进程的段号 temp->state = USEFUL;//占用 break;//找到即跳出链表循环,不需要再找了 } temp = temp->next; } } //更新可用表即可 updateAvail();//分区说明表已改,循环一遍重新建立可用表 flag = true; break; } else { flag = false; break; } } return flag; }
当碎片不够当前分配段的大小时,进行内存紧缩:
void compaction() {//内存紧缩技术 temp = head; int count = 0,address,length; //address放的是前一个内存分区说明表的起始地址 //length放的是前一个分区的长度 int itemCode = 0; Partition *link=new Partition; //连接不想练的两个占用块 while (temp) { Partition *ttemp; //释放空闲块结点所占内存(现实的内存) if (temp->state == NOUSE) { ttemp = temp; temp = temp->next; delete ttemp;//将空闲结点的释放掉 } else if (temp->state == USEFUL) { if (count == 0) {//如果是第一个用了的 address = 0; temp->address = address; temp->itemCode = itemCode;//分区说明表项 号 这个会变 itemCode++; length = temp->length; count++; head = temp;//将这个变为头结点 link = temp; } else {//如果不是第一个占用结点 address += length; temp->itemCode = itemCode; itemCode++; temp->address = address;//当前分区的起始地址是上一个起始地址加它分区的长度 length = temp->length;//下一个用 link->next = temp;//把修改后的链接起来 link = temp; } tail = temp; temp = temp->next; } } Partition *newNode=new Partition;//上面紧缩后 将剩余的空闲空间搞成一个大的空闲结点链接上 newNode->address = address + length; newNode->itemCode = itemCode; newNode->itemCode = tail->itemCode + 1; newNode->length = total - newNode->address; newNode->next = nullptr; newNode->processName = " "; newNode->segnum = -1; newNode->state = NOUSE; tail->next = newNode; tail = newNode; //更新可用表 updateAvail(); }
若空闲区仍然不够,就需要进行淘汰函数:
//淘汰函数 void eliminate(int size) { temp = head; int min=INT_MAX; vector<string> processName;//将要被淘汰段的进程名 数组 vector<int> segnum;//将要被淘汰段的段号 数组 int elimSize=0;//淘汰段的总长度 for (int i = 0; i < available.size(); i++) { elimSize += available[i].length; } Partition *mintemp=new Partition; while (elimSize < size) {//如果淘汰段的总长度小于要调入的段的长度 //循环淘汰 temp=head; min=INT_MAX; while (temp) {//挑出一个访问次数最小的淘汰 auto segtable = processTable[temp->processName].segTable;//获得该进程的段表 if (segtable[temp->segnum].visitCount < min&&temp->state==USEFUL) {//获得访问次数最小段的信息 如果最小的有好几个 这种比较下淘汰第一个 min = segtable[temp->segnum].visitCount; mintemp = temp; } temp = temp->next; } mintemp.state=NOUSE; processName.push_back(mintemp->processName);//将每次找出来的最小段的相关信息存入 动态数组 segnum.push_back(mintemp->segnum); elimSize += processTable[mintemp->processName].segTable[mintemp->segnum].segLength; } //进行淘汰 temp = head; int i = 0; while (temp) { if (temp->processName == processName[i]&&temp->segnum==segnum[i]) { //淘汰要更新段表 processTable[temp->processName].segTable[temp->segnum].visitCount = 0;//要淘汰的段 访问次数清0 processTable[temp->processName].segTable[temp->segnum].startAdd = -1; processTable[temp->processName].segTable[temp->segnum].state = 0;//在外存 //淘汰后的段进入请求表中 Request newReq; newReq.processName = temp->processName; newReq.segnum = temp->segnum; newReq.length = temp->length; request.push_back(newReq); printf("\n淘汰了:进程名为:%s段号为:%d段长为:%d\n\n", temp->processName.c_str(), temp->segnum,temp->length); temp->state = NOUSE;//进行具体的淘汰操作 temp->processName = " "; temp->segnum = -1; i++; if (i == processName.size()) { break;//如果已经把要淘汰的都淘汰了就提前出去 } } temp = temp->next; } //淘汰完了有空闲区进行紧缩 compaction();//紧缩完了里面就更新了可用表 }
内存释放后,进行合并空闲区算法:
void Mergeidle() {//合并空闲区 temp = head; Partition *del; int itemCode = 0; while (temp) { if (temp->state == NOUSE && temp->next != nullptr&&temp->next->state == NOUSE) {//当前结点为空闲,下一节点也为空闲且不为空指针 if (temp->next->next != nullptr&&temp->next->next->state == NOUSE) { //下下结点为空闲且不为空指针,合并三个空闲区为一个 temp->length += temp->next->length + temp->next->next->length; temp->processName = " "; del = temp->next; temp->next = del->next; delete del; del = temp->next; temp->next = del->next; delete del; } else {//合并两个空闲区为一个 temp->length += temp->next->length; temp->processName = " "; del = temp->next; temp->next = del->next; delete del; } } temp->itemCode = itemCode; itemCode++; temp = temp->next; } }
测试用例,运行结果与运行情况分析;
测试情况一:
最先适应法测试:
分配前内存占用情况:
为进程 b 的 0 段(段长 11)分配后内存占用情况:
运行结果符合最先适应算法的预期结果
测试情况二:
最佳适应算法:
分配前内存占用情况:
为进程 d 的 0 段(段长 25)分配后内存占用情况:
运行结果发现,进程 d 的 0 段没有占用地址在前的空闲段(区号 0,空闲区长度 52),而是在空闲区(区号 3,空闲区长 32)占了一部分的空闲区,因为空闲区 3 的长度小于空闲区 0 的长度,这符合最佳适应算法的特点。
测试情况三:
最坏适应算法:
分配前内存占用情况:
为进程 a 的 3 段(段长 15)分配后内存占用情况:
运行结果发现,进程 a 的 3 段没有占用地址在前的空闲段(区号 1,空闲区长度 22),而是在空闲区(区号 3,空闲区长 23)占了一部分的空闲区,因为空闲区 3 的长度大于空闲区 1 的长度,这符合最坏适应算法的特点。
测试情况四:
内存紧缩:
分配前内存占用情况:
为进程 w 的 0 段(段长 30)分配内存后内存占用情况:
分配前,内存中没有大于等于 30 的空闲区,当为给段长为 30 的段分配时,正常分配失败后,就对内存进行紧缩,从而得到等于 30 的内存空间,得以放下段长为 30 的段。
测试情况五:
段的淘汰
分配前内存占用情况(其中进程 a 的段 2 访问次数为 2,其他均为 1):
为进程 b 的 0 段(段长 40)分配内存后内存占用情况:
它淘汰了访问次数较少的进程 a 的 3 段和进程 d 的 0 段,释放了大小为 40 的内存区,使得进程 b 的 0 段能够又足够的内存空间调入。
测试情况六:
内存的释放:
在未进行段的释放前,内存空间情况:
当释放了进程 b 时,内存中出现两个连续的空闲区,合并未一个空闲区,结果如下:
当释放进程 a 时,a 的所有段都被释放,出现三个连续的空闲区,合并一个空闲区,结果如下:
合并空闲区程序运行结果符合预期。
6 自我评价与总结:
特点:本次编写的程序结构较为清晰,主要围绕分区说明链表进行操作,主要功能通过五个函数分别实现主要功能。在完成最坏适应算法进行段式存储的基础上,又完成了最先适应算法和最佳适应算法,还实现了内存紧缩和最近未使用算法(LFU)段淘汰算法进行当内存空闲区不够大使得段的分配。
结构体的使用使得整个程序显得更加精致有序,进程表、段表用 map 表示大大简化了查找进程、进程中段的操作。总之,这个程序既能够通过文件初始化进程数,每个进程的段数及段大小,又能自己添加进程等信息,每当进程提出申请空间的大小后,可选择一种分配算法进行段的分配,以及显示分配资源后有关内存空间的使用情况,还可以进行内存紧缩和段的淘汰。可以说较为完整的模拟了一个段式存储管理器。
需改正之处:
编写代码时考虑有些细节没考虑清楚或忽略了一些细节就进行了代码的编写,造成完成代码进行测试时,花费了较长时间进行 Debug;有些重复的代码,本可以写成函数,减少代码的重复,这些都是未能在一开始完善设计,严谨思考后在进行编写代码的后果,今后在写代码前应多花时间在程序的设计上,尤其当结构体的数据项多,结构体之间的联系多的程序中,更要认真细心的进行设计,去考虑细节,完善程序。
收获:
这次课程设计让我对操作系统中的段式存储管理的知识有了更深入的学习,也对操作系统中很重要的虚拟存储器有了更全面的认识。
在编写程序中,我提高了我分析问题,并根据需求转化为相应程序结构的能力,丰富了自己编写程序,调时程序的经验。编程程序难免出现 bug,当出现 bug 时,要认真细心的去 debug,同时思考出现异常的表象可能哪里出现错误导致的,debug 时跟踪每一步的变化,进行分析判断,认真细心,就没有改不出的 bug。
其他方法:
段式存储管理中的段的内存分配不仅可以用最坏适应算法,也可以使用最先适应算法和最佳适应算法,前面已描述过这两个算法的思想;而段式存储管理中当没有足够空闲区时,淘汰内存中的段时,不仅可以用 LFU 算法,也可以用 FIFO 算法:选择留在内存驻留时间最长的页将其淘汰或 NUR(最近没有使用页面淘汰算法):从那些最近一个时期未被访问的页中任选一页淘汰。
就进行了代码的编写,造成完成代码进行测试时,花费了较长时间进行 Debug;有些重复的代码,本可以写成函数,减少代码的重复,这些都是未能在一开始完善设计,严谨思考后在进行编写代码的后果,今后在写代码前应多花时间在程序的设计上,尤其当结构体的数据项多,结构体之间的联系多的程序中,更要认真细心的进行设计,去考虑细节,完善程序。
收获:
这次课程设计让我对操作系统中的段式存储管理的知识有了更深入的学习,也对操作系统中很重要的虚拟存储器有了更全面的认识。
在编写程序中,我提高了我分析问题,并根据需求转化为相应程序结构的能力,丰富了自己编写程序,调时程序的经验。编程程序难免出现 bug,当出现 bug 时,要认真细心的去 debug,同时思考出现异常的表象可能哪里出现错误导致的,debug 时跟踪每一步的变化,进行分析判断,认真细心,就没有改不出的 bug。
其他方法:
段式存储管理中的段的内存分配不仅可以用最坏适应算法,也可以使用最先适应算法和最佳适应算法,前面已描述过这两个算法的思想;而段式存储管理中当没有足够空闲区时,淘汰内存中的段时,不仅可以用 LFU 算法,也可以用 FIFO 算法:选择留在内存驻留时间最长的页将其淘汰或 NUR(最近没有使用页面淘汰算法):从那些最近一个时期未被访问的页中任选一页淘汰。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。