1.1 设计目的
理解存储管理的功能,掌握动态异长分区内存管理中的最佳适应算法。
1.2 设计要求
本设计要求模拟最佳适应算法的分配算法和回收算法。
1.3 数据结构设计
空闲区域首址 | 空闲区域长度 |
… | … |
addr | size |
… | … |
图1-1 空闲区域表 |
为了实现存储资源的分配和回收,操作系统需要记录内存资源使用情况,即哪些区域尚未分配,哪些区域已经分配以及分配给哪些进程等。为此一般需要两个表,一个为分配表, 另外一个为空闲区域表。前者记录已经分配的区域, 后者记录着所有当前未被进程占用的空闲区域, 如图1-1所示。
显然, 没有记录于表中的区域即为已被进程所占用的非空闲区域,在实际的操作系统中,这些区域登记在进程的PCB中。而PCB中除了关于内存资源的信息外,还有其它大量信息。
由于本设计是对存储管理算法的模拟,所以用一个线程来代表一个进程,用线程驻留区域表来描述线程占用的内存空间,如图1-2所示。
线程名称 | 驻留区始址 | 驻留区大小 |
a | 0 | 15 |
b | 20 | 10 |
…… | …… | …… |
图1-2 线程驻留区表 |
同时,需要一张表来记录各个线程对内存的请求信息,如图1-3所示。
线程名称 | 请求大小(KB) | 预计驻留时间( 秒) |
thread_1 | 15 | 8 |
thread_2 | 11 | 4 |
…… | …… | …… |
图1-3 内存申请表 |
1.4 算法设计
1.4.1 算法结构设计
分配时取满足申请要求且长度最小的空闲区域。在实现时, 可将系统中所有的空闲区域按照长度由小到大的次序依次记录于空闲区域表中。与最先适应算法相类似, 当进程申请存储空间时, 系统由表的头部开始查找, 取第一个满足要求的表目。如果该表目所对应的区域长度恰好与所申请的区域长度相同, 则将该区域全部分配给申请者。否则将该区域分割为两部分, 一部分的长度与所申请的长度相同, 将其分配给申请者;另一部分即剩余部分的长度为原长度与分配长度之差, 由于剩余部分的长度已经改变,所以应重新将其按长度从小到大的顺序插入到空闲区域表中。
回收时,不仅要按回收区的首地址查询空闲区表,找到与回收区相临的空闲区,将其合并到相临区中,而且要把合并后的回收区按照长度递增的顺序插入到空闲区域表的适当位置。
1.4.2 设计并分析测试数据
假设初始内存布局如图1-4,图中的起始地址以及大小都以KB来衡量。
起始地址 | 0 | 15 | 20 | 30 | 40 | 70 | 85 | 105 | 135 | 180 |
占用者 | a | b | c | d | e | |||||
大 小 | 15 | 5 | 10 | 10 | 30 | 15 | 20 | 30 | 45 | 20 |
图1-4初始内存布局 |
由图1-4可见,初始时共有五个线程驻留在内存,它们是a,b,c,d,e,线程驻留区表如图1-5;还有五个空闲区,空闲区域表如图1-6。
假设现在有三个线程提出内存申请,申请情况见图1-7。经过分析我们得到在每种分配算法下这三个线程所申请到的内存情况。图1-8是最佳适应算法分配情况。
线程名称 | 驻留区始址 | 驻留区大小 |
a | 0 | 15 |
b | 20 | 10 |
c | 40 | 30 |
d | 85 | 20 |
e | 135 | 45 |
图1-5 线程驻留区表 |
空闲区首址 | 空闲区长度 |
15 | 5 |
30 | 10 |
70 | 15 |
105 | 30 |
180 | 20 |
图1-6 空闲区域表 |
线程名称 | 请求大小(KB) | 预计驻留时间( 秒) |
thread_1 | 15 | 8 |
thread_2 | 11 | 4 |
thread_3 | 3 | 5 |
图1-7 内存申请表 |
线程名称 | 驻留区始址 | 驻留区大小 |
a | 0 | 15 |
b | 20 | 10 |
40c30 | 40 | 30 |
d | 85 | 20 |
e | 135 | 45 |
thread_1 | 70 | 15 |
thread_2 | 70 | 11 |
thread_3 | 15 | 3 |
图1-8 最佳适应算法线程驻留区表 |
1.4.3 程序设计
程序包含两个文件,一个是头文件variable_partition.h,另一个是源程序文件variable_partition.cpp。在头文件中定义了宏、数据结构、全局变量、函数声明,源程序中含有各个函数的实现。
在头文件中,结构体FREEAREA、REQUIRE_MEMORY、THREAD_RESIDENCE_MEMORY分别对应于图1-1、图1-2、图1-3中的一行,不同之处是为了构成链表在三个结构体中都有前向指针。数组init_free_area_table对应于图1-6,数组init_thread_require_memory_table对应于图1-5,数组init_thread_residence_memory_table对应于图1-7,为了实现动态分配与释放,用链表重新组织空闲区域表、线程驻留区表和内存申请表,全局变量p_free_area_list是空闲区链首,p_thread_require_memory_queue是内存申请队列的队首,p_thread_residence_memory_list是线程驻留区链首,tail_thread_residence_memory_list是线程驻留区链尾,由于线程驻留区链表被内存分配函数和内存释放函数共享,故用临界区变量CS_THREAD_MEMORY_LIST来保护,同理,屏幕是所有线程共享的,所以用临界区变量CS_SCREEN来保护,空闲区链表被内存分配函数和内存释放函数共享,故用临界区变量CS_FREEAREA_LIST来保护。h_thread是线程句柄数组,用来存放各个线程的句柄。
程序共包含13个函数,按照作用可以将它们分成五组。
第一组包括函数print_space()和函数display_thread_residence_memory(),前者用来显示若干个空格,后者用来显示线程驻留区表;
第二组共十一个函数,用来实现最佳适应分配法,它们的名称及功能如图1-9。
函数名称 | 函数功能 |
BF_initialize_freearea_list | 初始化空闲区链表:按长度递增排序 |
BF_delete_freearea_list | 删除空闲区链表 |
BF_initialize_require_memory_list | 初始化内存申请链表 |
BF_delete_require_memory_list | 删除内存申请链表 |
BF_initialize_thread_residence_memory_list | 初始化线程驻留区链表 |
BF_delete_thread_residence_memory_list | 删除线程驻留区链表 |
BF_thread | 线程函数:申请内存;驻留内存;释放内存 |
BF_require_memory | 申请一段内存,成功时返回起始地址,失败时返回空 |
BF_release_memory | 释放一段内存 |
BF_insert_freearea | 将空闲区按大小递增的次序插入到空闲区链表中 |
BF() | 初始化函数:创建线程并等待它们结束 |
图1-9 最佳适应分配法的函数及其功能 |
1.4.4 程序流程图设计
1.函数关系调用图
图1-10 函数关系调用图
2.主要程序流程图
图1-11 主要程序流程图
3.内存申请函数流程图
图1-12 内存申请函数流程图
4.内存释放函数流程图
图1-13 内存释放函数流程图
1.5 程序代码
内存申请函数
// 最佳适应分配算法的内存申请函数
int BF_require_memory(int size) {
FREEAREA *p = p_free_area_list;
FREEAREA *prev = NULL;
int best_fit_start = -1;
int best_fit_size = INT_MAX;
// 寻找最佳适应的空闲区
while (p != NULL) {
if (p->size >= size && p->size < best_fit_size) {
best_fit_start = p->start_address;
best_fit_size = p->size;
prev = p;
}
p = p->next;
}
// 如果找到了最佳适应的空闲区
if (best_fit_start != -1) {
// 划分空闲区
if (best_fit_size > size) {
FREEAREA *new_free_area = (FREEAREA *)malloc(sizeof(FREEAREA));
new_free_area->next = prev->next;
new_free_area->start_address = best_fit_start + size;
new_free_area->size = best_fit_size - size;
prev->next = new_free_area;
}
// 从空闲区链表中删除已分配的空闲区
if (prev == NULL) {
p_free_area_list = p_free_area_list->next;
} else {
prev->next = prev->next->next;
}
return best_fit_start;
}
return -1; // 内存申请失败
}
内存释放函数
// 最佳适应分配算法的内存释放函数
void BF_release_memory(int start_address,int size){
EnterCriticalSection(&CS_FREEAREA_LIST);
FREEAREA *temp, *p, *pp;
p=p_free_area_list;
temp = new FREEAREA;
temp->start_address=start_address;
temp->size=size;
temp->next=NULL;
int flag=0;
while(1) {
if(p->next!=NULL&&start_address+size<=p->next->start_address&&start_address>=p->start_address+p->size) {
temp->next=p->next;
p->next=temp;
flag=1;
}
if(flag==1||p->next==NULL) break;
p=p->next;
}
if(start_address>=p->start_address+p->size) {
if(flag==0)
p->next=temp;
} else {
if(flag==0) {
pp=p_free_area_list;
temp->next=pp;
pp->next=p_free_area_list->next;
p_free_area_list=temp;
}
}
FREEAREA *ppp;
ppp=p_free_area_list;
while(1) {
if(ppp->next!=NULL) {
if(ppp->start_address+ppp->size==ppp->next->start_address) {
ppp->size+= ppp->next->size;
if(ppp->next->next!=NULL)
ppp->next=ppp->next->next;
else {
ppp->next=NULL;
}
continue;
}
}
if(ppp->next!=NULL) ppp=ppp->next;
else break;
}
EnterCriticalSection(&CS_SCREEN);
THREAD_RESIDENCE_MEMORY *q;
q = p_thread_residence_memory_list;
if (q->start_address == start_address) {
p_thread_residence_memory_list = p_thread_residence_memory_list->next;
} else {
while (q->next != NULL) {
if (q->next->start_address == start_address) {
if (q->next == tail_thread_residence_memory_list) {
tail_thread_residence_memory_list = q;}
q->next = q->next->next;
break;
}
q = q->next;
}
}
LeaveCriticalSection(&CS_SCREEN);
LeaveCriticalSection(&CS_FREEAREA_LIST);
}
1.6 运行结果
1.6.1 运行结果截图
图1-14 运行结果一
图1-15 运行结果二
1.6.2 运行结果分析
第一次运行: 线程thread_1: 请求内存成功,分配了大小为15 KB的内存,起始地址为70 KB。 显示线程内存驻留区链表。 线程thread_2: 请求内存成功,分配了大小为11 KB的内存,起始地址为70 KB。 显示线程内存驻留区链表。 线程thread_3: 请求内存成功,分配了大小为3 KB的内存,起始地址为15 KB。 显示线程内存驻留区链表。 模拟结束后: 所有线程执行完毕后,显示线程内存驻留区链表。 |
第二次运行: 线程thread_2: 请求内存成功,分配了大小为11 KB的内存,起始地址为70 KB。 显示线程内存驻留区链表。 线程thread_3: 请求内存成功,分配了大小为3 KB的内存,起始地址为15 KB。 显示线程内存驻留区链表。 线程thread_1: 请求内存成功,分配了大小为15 KB的内存,起始地址为70 KB。 显示线程内存驻留区链表。 模拟结束后: 所有线程执行完毕后,显示线程内存驻留区链表。 |
对于两次运行结果不同做以下原因分析: 每次运行结果不同的原因主要是因为程序中使用了多线程,多线程程序的执行顺序是不确定的。在多线程环境中,操作系统的调度器决定了每个线程何时获得 CPU 时间。不同运行中线程的交替执行顺序可能不同,从而导致不同的结果。 程序实现了一个使用线程的内存分配和释放算法--最佳适应算法。线程被创建来模拟不同的进程,这些进程或任务请求和释放内存。线程的并发执行,加上潜在的竞争条件和线程争用,可能导致每次运行中内存分配和释放的顺序不同。 |
1.7 设计总结
在设计最佳适应算法的动态异长分区内存管理系统时,理解了存储管理的关键功能,并掌握了最佳适应算法的分配和回收过程。在数据结构设计方面,建立了两张关键表:分配表和空闲区域表。分配表记录已经分配的内存区域,而空闲区域表则记录当前未被进程占用的空闲内存区域。通过线程驻留区域表和内存申请表,成功地将进程与其内存请求信息关联起来,以便模拟实际存储管理情境。在算法设计方面,最佳适应算法的核心思想是分配时选择满足要求且长度最小的空闲区域。实现了按照空闲区域长度由小到大的顺序记录空闲区域表,并在分配时从表头开始查找符合条件的区域。若找到匹配的区域,根据申请大小进行分配或分割,并重新排序插入剩余部分。在回收过程中,实现了按照回收区的首地址查询空闲区表,找到相邻的空闲区并合并。然后,将合并后的回收区按长度递增的顺序插入到空闲区域表的适当位置。同时每次运行结果不同的原因主要是因为程序中使用了多线程,多线程程序的执行顺序是不确定的。在多线程环境中,操作系统的调度器决定了每个线程何时获得 CPU 时间。不同运行中线程的交替执行顺序可能不同,从而导致不同的结果,对于操作系统中进程的并发有了更好的理解。