操作系统实验之内存管理
一、实验目的
独立地设计几个常用的存储分配算法,并用高级语言编写程序对各种算法进行分析比较,评测其性能的优劣,从而加深对这些算法的了解。
二、实验内容
用C语言独立编写分区分配算法、回收算法、请求式分页分配算法。在请求式分页分配算法中,通过程序的执行结果来分析计算不同页面淘汰算法情况下的访问命中率,并以此来比较各种算法的优劣。
三、实验步骤
3.1 任务分析
3.1.1 实验任务
1.分区分配算法
本实验要求采用首次适应算法和最佳适应算法两种分区分配的内存管理算法。
(1) 建立分区描述器:分区描述器可根据自己编写程序的需要来建立,描述器本身所包含的内容以描述清楚内存分区情况为准。
(2) 建立自由主存队列:针对两种不同的放置策略(首次适应和最佳适应)来建立相应的队列结构。
(3) 用C语言编写实现首次适应算法和最佳适应算法的程序。
(4) 用C语言编写回收算法。
2.请求式分页存储管理算法
本实验要求采用请求式分页存储算法,淘汰算法采用先进先出算法FIFO和最近最少使用页面淘汰算法(LRU)。
设逻辑空间大小为128K,页面尺寸分别为2、4、6、8、10、12、14、16K,内存容量为8至64页。
(1) 先进先出算法FIFO:该算法的实质是选择作业中在主存驻留时间最长的一页淘汰,这种算法容易实现,例如分配一个作业的存储块数为m,则只需建立一张m个元素的队列表Q(0)、Q(1)、…、Q(m-1)和一个替换指针。这个队列是按页调入主存的一页。如图4-1所示,某时刻调入主存四个块,(即m=4),它们按页进入主存的先后顺序为4、5、1、2,当需要置换时,总是淘汰替换指针所指向的那一页。
新调进的页面装入主存后,修改相应的队列元素,然后将替换指针往前移动,使其指向当前最老的一页。
(2) 最近最少使用页面淘汰算法(LRU):这是一种经常使用的方法,有多种不同的实施方案。这里采用不断调整页表链的方法,即总是淘汰页表链链首的页面,而把新访问的页面插入链尾。如果当前调用页面已在页表内,则把它再次调整到链尾。这样就能保证最近使用的页面总处于靠近链尾部分,而不常使用的页面被移到链首,逐个被淘汰。
3.1.2 测试数据
测试数据1:首次适应算法
- 选择首次适应算法(输入:1)
- 分配内存(输入:20)
- 分配内存(输入:10)
- 释放内存(输入:1)
- 分配内存(输入:15)
- 退出程序(输入:0)
期望结果:
第一次分配应将整个内存块分为两部分:20KB已分配和1004KB空闲。第二次分配应从空闲块中选择第一个满足大小的部分(15KB),将其标记为已分配。释放第一个分区后,应该合并两个相邻的空闲块。
测试数据2:最佳适应算法
- 选择最佳适应算法(输入:2)
- 分配内存(输入:20)
- 分配内存(输入:10)
- 释放内存(输入:0)
- 分配内存(输入:15)
- 退出程序(输入:0)
期望结果:
第一次分配应选择最佳适应块,将整个内存块分为两部分:20KB已分配和1004KB空闲。第二次分配应选择剩余的最佳适应块(10KB),将其标记为已分配。释放第一个分区后,应该合并两个相邻的空闲块。
请求式分页存储管理算法
- 输入形式和输入值范围: 用户首先输入要进行的操作:1、先进先出算法;2、最近最少使用页面淘汰算法;0、退出程序。若选择1或2,用户随后输入页面走向和物理块数。
- 输出形式:程序输出页面置换算法的执行结果,包括页面走向、物理块状态矩阵、缺页中断情况、缺页率
- 程序能达到的功能:实现先进先出(FIFO)和最近最少使用(LRU)两种页面置换算法。根据用户选择,执行相应的页面置换算法并输出结果。
- 测试数据:
先进先出算法测试
输入:1
输入:11 3
输入:1 8 9 7 5 5 6 6 9 1 9
期望输出:
页面走向 | 1 | 8 | 9 | 7 | 5 | 5 | 6 | 6 | 9 | 1 | 9 |
物理块1 | 1 | 1 | 1 | 7 | 7 | 7 | 7 | 7 | 9 | 9 | 9 |
物理块2 | . | 8 | 8 | 8 | 5 | 5 | 5 | 5 | 5 | 1 | 1 |
物理块3 | . | . | 9 | 9 | 9 | 9 | 6 | 6 | 6 | 6 | 6 |
缺页中断 | * | * | * | * | * |
| * |
| * | * |
|
缺页率:72.73%
最近最少使用算法测试:
输入:2
输入:11 3
输入:1 8 9 7 5 5 6 6 9 1 9
期望输出:
页面走向 | 1 | 8 | 9 | 7 | 5 | 5 | 6 | 6 | 9 | 1 | 9 |
物理块1 | 1 | 1 | 1 | 7 | 7 | 7 | 7 | 7 | 9 | 9 | 9 |
物理块2 | . | 8 | 8 | 8 | 5 | 5 | 5 | 5 | 5 | 1 | 1 |
物理块3 | . | . | 9 | 9 | 9 | 9 | 6 | 6 | 6 | 6 | 6 |
缺页中断 | * | * | * | * | * |
| * |
| * | * |
|
缺页率:72.73%
3.2概要设计
1)分区分配算法
抽象数据类型的定义:
ElemType:内存块元素类型,包括内存块的大小、起始地址和状态(空闲或已分配)。
DuLNode:双向链表节点类型,包括一个指向ElemType类型的数据和两个指针,分别指向前驱节点和后继节点。
DuLinkList:双向链表类型,包括一个指向链表第一个节点的指针和一个指向链表最后一个节点的指针。
主程序流程:
- 初始化内存块链表
- 用户输入选择的内存分配算法
- 进入循环,用户可以选择分配内存、释放内存或退出程序。
- 如果选择分配内存,根据用户选择调用相应的分配算法(首次适应算法或最佳适应算法)。
- 如果选择释放内存,用户输入要释放的分区编号,调用释放内存函数。
- 如果选择退出程序,跳出循环。
- 在循环中,每次操作后都会显示当前内存分配情况。
程序模块调用关系:
main()是程序的入口函数,负责控制程序流程和用户输入,调用其他函数完成相应操作。InitBlock()用于初始化内存块链表。Alloc()用于分配内存,根据用户选择调用FirstFit()或BestFit()。Free()用于释放内存,同时会合并相邻的空闲块。Show()用于显示当前内存分配情况。FirstFit()和BestFit()是内存分配算法函数,根据不同的算法分配内存块。
- 请求式分页存储管理算法
定义数据结构: 定义结构体 PRA 用于存储页面置换算法的相关信息。page(表示页面的字符)、interrupt(表示是否发生页面错误的字符)和time(表示自上次访问以来的时间)
全局变量定义:
ans[MAX][MAX] 是一个类型为PRA的二维数组,用于存储每个时间步骤每个内存块的页面信息。
pra[MAX] 是一个类型为PRA的数组,用于存储整个页面序列的页面信息。
n 和 m 是表示页面总数和内存块数的整数。
主程序流程:
- 用户选择算法后,程序读取页面走向和物理块数。
- 初始化状态矩阵和页面信息数组。
- 遍历页面走向,执行页面置换算法,并更新状态矩阵和页面信息数组。
- 输出页面置换结果,包括页面走向、状态矩阵、中断情况、缺页率。
程序模块调用关系:
在 pageReplace 函数中,通过调用 hashad、hasempty 和 wheremax 函数,实现了页面置换算法的核心逻辑。主程序 main 负责用户交互和选择,根据用户的选择调用相应的页面置换算法。
3.3详细设计
1)分区分配算法
主要函数设计:
main():主函数,控制程序流程和用户输入
InitBlock():初始化内存块链表函数。
Alloc(int ch):分配内存函数,根据用户选择调用相应的分配算法。
FirstFit(int request):首次适应算法函数。
BestFit(int request):最佳适应算法函数
Free(intflag):释放内存函数。
Show():显示内存分配情况函数。
main():主函数。
2)请求式分页存储管理算法
过程初始化矩阵和数组:对每个 i 从 0 到 MAX-1:对每个 j 从 0 到 MAX-1:ans[i][j].page <- '.'ans[i][j].interrupt <- ' 'ans[i][j].time <- 0对每个 i 从 0 到 MAX-1:pra[i].page <- '.'pra[i].interrupt <- ' 'pra[i].time <- 0过程hashad(i, temp) 返回整数值:对每个 u 从 0 到 m-1:如果 ans[u][i].page 等于 temp:返回 u返回 -1
过程hasempty(i) 返回整数值:对每个 u 从 0 到 m-1:如果 ans[u][i].page 等于 '.':返回 u返回 -1过程wheremax(i) 返回整数值:had <- 0maxtime <- ans[0][i].time对每个 u 从 1 到 m-1:如果 ans[u][i].time 大于 maxtime:had <- umaxtime <- ans[u][i].time返回 had
过程pageReplace(ca):调用初始化矩阵和数组过程输出 "请输入页面走向和物理块数:"输入 n, m输出 "请输入%d个字符:", n对每个 i 从 0 到 n-1:输入 pra[i].pageans[0][0] <- pra[0]pra[0].interrupt <- '*'ans[0][0].time <- 0对每个 i 从 1 到 n-1:对每个 p 从 0 到 m-1:如果 ans[p][i-1].page 不等于 '.':ans[p][i] <- ans[p][i-1]ans[p][i].time <- ans[p][i].time + 1temp <- pra[i].pageishad <- 调用hashad(i-1, temp)如果 ishad 大于等于 0:pra[i].interrupt <- ' 'ans[ishad][i].time <- 0继续下一轮循环isempty <- 调用hasempty(i-1)如果 isempty 大于等于 0:ans[isempty][i] <- pra[i]pra[i].interrupt <- '*'ans[isempty][i].time <- 0继续下一轮循环where <- 调用wheremax(i-1)ans[where][i] <- pra[i]pra[i].interrupt <- '*'ans[where][i].time <- 0输出 "%s置换算法:", 如果 ca 等于 1 则输出 "FIFO" 否则输出 "LRU"输出 "%8s:", "页面走向"对每个 r 从 0 到 n-1:输出 "%3c ", pra[r].page输出 换行线
主要函数设计:
hashad(int i, char temp): 判断某页是否在矩阵的特定列中存在。如果找到,它返回内存块的索引;否则返回-1
hasempty(int i): 查找矩阵的特定列中是否有空槽。如果找到第一个空块,则返回其索引;否则返回-1。
wheremax(int i): 查找时间步骤i中自上次访问以来具有最长时间的内存块,并返回其索引。
pageReplace(int ca): 执行页面置换算法,根据用户选择执行 FIFO 或 LRU 算法。
主程序 main():
进入无限循环,等待用户输入操作选项。
用户选择 1 或 2 时,调用 pageReplace(int ca) 函数执行页面置换算法。
用户选择 0 时,退出程序。
3.4调试分析
1)分区分配算法
改进设想:
- 动态调整内存块大小:当内存块被释放后,如果相邻的空闲块也是空闲的,可以考虑将它们合并为一个更大的空闲块。当有新的内存请求时,可以优先考虑使用已合并的较大空闲块,提高内存利用率。
- 引入内存回收策略:当连续多个空闲块之间没有被分配的内存时,可以考虑回收这些空闲块,以减少内存碎片化。
2)请求式分页存储管理算法
改进设想:
- 输入验证:在输入页面走向和物理块数时,添加输入验证,确保输入的值在合理范围内。
- 错误处理:虑在程序中添加错误处理机制,处理可能出现的输入错误或异常情况。
- 算法性能优化:对于大规模输入,可以考虑优化页面置换算法的实现,提高算法执行效率。
3.5测试结果
1)分区分配算法
首次适应算法
最佳适应算法:
2)请求式分页存储管理算法
先进先出算法
最近最少使用页面淘汰算法
测试结果和预期相同
3.6测试说明
分区分配算法
选择内存分配算法
选择操作
输入要分配的内存大小
输入要释放的分区编号
请求式分页存储管理算法
运行程序,选择需要进行的操作
输入页面走向和物理块数
输入字符
回车运行,输出页面走向和缺页率
四、实验总结
通过本次实验,我学习了页面置换算法,包括先进先出(FIFO)和最近最少使用(LRU)两种算法,以及内存管理算法,包括首次适应算法和最佳适应算法。在实践中,我通过代码实现和调试更深刻地理解了这两种算法的工作原理和优缺点。
首次适应算法通过从内存起始位置开始搜索,找到第一个满足请求大小的空闲块进行分配。它的优点是简单、快速,适合处理大量临时内存请求。但它可能会导致内存碎片化问题,使得大块的连续内存难以分配。最佳适应算法通过从所有空闲块中选择大小最接近请求大小的块进行分配。它的优点是可以更好地利用内存空间,减少碎片化问题。但是由于需要遍历所有空闲块进行比较,可能会导致较高的时间复杂度。在实验中,我体会到不同内存管理算法在不同场景下的适用性。对于频繁申请和释放内存的场景,首次适应算法可能更合适,因为它的分配速度较快。而对于需要更好内存利用率的场景,最佳适应算法可能更合适,尽管它的分配速度可能较慢。
FIFO算法通过简单的先进先出原则,保证最早进入内存的页面被最早淘汰,从而解决了页面调度的基本问题。然而,FIFO算法可能导致Belady异常,即增加内存块数反而导致缺页率升高。LRU算法则根据页面的最近使用情况进行淘汰,更符合实际的页面访问模式,但实现相对复杂。在实验中,我认识到不同的页面置换算法在不同的场景下有各自的优劣势,需要根据具体应用情况进行选择
五、附录
分区分配算法
#include <stdio.h>
#include <stdlib.h>// 定义内存状态常量
#define MEMORY_FREE 0
#define MEMORY_BUSY 1
#define MEMORY_OK 1
#define MEMORY_ERROR 0
#define MAX_MEMORY_LENGTH 1024 // 最大内存长度// 定义内存块元素类型
typedef struct FreeArea {long size; // 内存块大小long address; // 内存块起始地址int state; // 内存块状态(空闲或已分配)
} ElemType;// 定义双向链表节点类型
typedef struct DuLNode {ElemType data;struct DuLNode* prior; // 前驱指针struct DuLNode* next; // 后继指针
} DuLNode, *DuLinkList;DuLinkList block_first; // 内存块链表的第一个节点
DuLinkList block_last; // 内存块链表的最后一个节点// 初始化内存块链表
int InitBlock() {// 创建两个哨兵节点,表示内存块链表的边界block_first = (DuLinkList)malloc(sizeof(DuLNode));block_last = (DuLinkList)malloc(sizeof(DuLNode));block_first->prior = NULL;block_first->next = block_last;block_last->prior = block_first;block_last->next = NULL;block_last->data.address = MAX_MEMORY_LENGTH;block_last->data.size = 0;block_last->data.state = MEMORY_BUSY;// 创建初始的整个内存块为一个空闲块DuLinkList freeBlock = (DuLinkList)malloc(sizeof(DuLNode));freeBlock->data.address = 0;freeBlock->data.size = MAX_MEMORY_LENGTH;freeBlock->data.state = MEMORY_FREE;freeBlock->prior = block_first;freeBlock->next = block_last;block_first->next = freeBlock;block_last->prior = freeBlock;return MEMORY_OK;
}// 分配内存
int Alloc(int ch) {int request = 0;printf("请输入要分配的大小(单位:KB):\n");scanf("%d", &request);if (request <= 0) {printf("无效的分配大小,请重试!\n");return MEMORY_ERROR;}// 根据用户选择调用相应的分配算法if (ch == 2) {if (BestFit(request) == MEMORY_OK)printf("分配成功\n");elseprintf("内存不足,分配失败\n");return MEMORY_OK;} else {if (FirstFit(request) == MEMORY_OK)printf("分配成功\n");elseprintf("内存不足,分配失败\n");return MEMORY_OK;}
}// 首次适应算法
int FirstFit(int request) {DuLinkList temp = NULL;DuLNode* p = block_first->next;while (p != block_last) {if (p->data.state == MEMORY_FREE && p->data.size >= request) {if (p->data.size == request) {// 如果找到的块大小刚好等于请求,直接分配整块p->data.state = MEMORY_BUSY;return MEMORY_OK;} else {// 如果找到的块大于请求,需要拆分temp = (DuLinkList)malloc(sizeof(DuLNode));temp->data.size = request;temp->data.state = MEMORY_BUSY;temp->prior = p;temp->next = p->next;temp->data.address = p->data.address;p->next->prior = temp;p->next = temp;p->data.address += request;p->data.size -= request;return MEMORY_OK;}}p = p->next;}return MEMORY_ERROR; // 没有找到合适的空闲块
}// 最佳适应算法
int BestFit(int request) {int minSize = MAX_MEMORY_LENGTH + 1; // 初始化为一个比最大内存还大的值DuLinkList bestFitBlock = NULL;DuLNode* p = block_first->next;// 寻找最佳适应块while (p != block_last) {if (p->data.state == MEMORY_FREE && p->data.size >= request && p->data.size < minSize) {bestFitBlock = p;minSize = p->data.size;}p = p->next;}// 如果没有找到适合的块if (bestFitBlock == NULL) {return MEMORY_ERROR;}// 如果找到的块大小刚好等于请求if (bestFitBlock->data.size == request) {bestFitBlock->data.state = MEMORY_BUSY;return MEMORY_OK;}// 如果找到的块大于请求,需要拆分DuLinkList temp = (DuLinkList)malloc(sizeof(DuLNode));temp->data.size = request;temp->data.state = MEMORY_BUSY;temp->data.address = bestFitBlock->data.address;temp->next = bestFitBlock;temp->prior = bestFitBlock->prior;bestFitBlock->prior->next = temp;bestFitBlock->prior = temp;bestFitBlock->data.address += request;bestFitBlock->data.size -= request;return MEMORY_OK;
}// 释放内存
int Free(int flag) {DuLNode* p = block_first->next;for (int i = 0; i < flag && p != block_last; i++)p = p->next;if (p == block_last)return MEMORY_ERROR;p->data.state = MEMORY_FREE;// 合并前面的空闲块if (p->prior != block_first && p->prior->data.state == MEMORY_FREE) {p->prior->data.size += p->data.size;p->prior->next = p->next;p->next->prior = p->prior;free(p);}// 合并后面的空闲块if (p->next != block_last && p->next->data.state == MEMORY_FREE) {p->data.size += p->next->data.size;DuLinkList next = p->next;p->next = p->next->next;p->next->prior = p;free(next);}return MEMORY_OK;
}// 显示内存分配情况
void Show() {int flag = 0;printf("\n内存分配情况:\n");printf("---------------------------------------------\n");DuLNode* p = block_first->next;printf("编号\t起始地址\t大小\t状态\n");while (p != block_last) {printf(" %d\t", flag++);printf(" %ld\t", p->data.address);printf(" %ldKB\t", p->data.size);if (p->data.state == MEMORY_FREE)printf("空闲\n");elseprintf("已分配\n");p = p->next;}printf("---------------------------------------------\n");
}// 主函数
int main() {int ch;printf("请输入内存分配算法:1 首次适应算法 2 最佳适应算法\n");scanf("%d", &ch);while (ch < 1 || ch > 2) {printf("请输入1或2:\n");scanf("%d", &ch);}InitBlock(); // 初始化内存块链表int choice;while (1) {Show(); // 显示当前内存分配情况printf("请输入操作:1:分配内存 2:释放内存 0:退出\n");scanf("%d", &choice);if (choice == 1)Alloc(ch); // 分配内存else if (choice == 2) {int flag;printf("请输入要释放的分区编号:\n");scanf("%d", &flag);Free(flag); // 释放内存} else if (choice == 0)break; // 退出循环else {printf("输入有误,请重试\n");continue;}}return 0;
}
请求式分页存储管理算法
#include <stdio.h>
#include <string.h>
#define MAX 100typedef struct page_replacement_algorithm {char page;char interrupt;int time;
} PRA;PRA ans[MAX][MAX]; // 存储页面置换详情的矩阵
PRA pra[MAX]; // 存储页面详情的数组
int n, m; // 页面总数和内存块数// 判断某页是否在矩阵的特定列中存在
int hashad(int i, char temp) {for (int u = 0; u < m; u++) {if (ans[u][i].page == temp) {return u; // 返回页面存在的索引}}return -1; // 如果页面不存在,返回-1
}// 查找矩阵的特定列中是否有空槽
int hasempty(int i) {for (int u = 0; u < m; u++) {if (ans[u][i].page == '.') {return u; // 返回空槽的索引}}return -1; // 如果没有空槽,返回-1
}// 查找特定行中时间值最大的列的索引
int wheremax(int i) {int had = 0, maxtime = ans[0][i].time;for (int u = 1; u < m; u++) {if (ans[u][i].time > maxtime) {had = u; // 如果找到更大的时间值,更新索引maxtime = ans[u][i].time;}}return had; // 返回时间值最大的索引
}// 使用先进先出(FIFO)或最近最少使用(LRU)算法执行页面置换
void pageReplace(int ca) {memset(ans, 0, sizeof(ans)); // 将矩阵初始化为零printf("请输入页面走向和物理块数:");scanf("%d %d", &n, &m);getchar();printf("请输入%d个字符:", n);for (int i = 0; i < n; i++) {scanf("%c", &pra[i].page);getchar();}// 将矩阵初始化为'.',表示空槽for (int i = 0; i < MAX; i++)for (int p = 0; p < MAX; p++)ans[i][p].page = '.';// 初始化第一个页面并将其中断标志设置为'*'ans[0][0] = pra[0];pra[0].interrupt = '*';ans[0][0].time = 0;// 遍历页面并执行页面置换for (int i = 1; i < n; i++) {for (int p = 0; p < m; p++) {if (ans[p][i - 1].page != '.') {ans[p][i] = ans[p][i - 1];ans[p][i].time++;}}char temp = pra[i].page;int ishad = hashad(i - 1, temp);if (ishad >= 0) {pra[i].interrupt = ' ';ans[ishad][i].time = 0;continue;}int isempty = hasempty(i - 1);if (isempty >= 0) {ans[isempty][i] = pra[i];pra[i].interrupt = '*';ans[isempty][i].time = 0;continue;}int where = wheremax(i - 1);ans[where][i] = pra[i];pra[i].interrupt = '*';ans[where][i].time = 0;}// 打印页面置换结果printf("%s置换算法:\n", ca == 1 ? "FIFO" : "LRU");printf("%8s:", "页面走向");for (int r = 0; r < n; r++)printf("%3c ", pra[r].page);printf("\n---------------------------------------------------------\n");for (int i = 0; i < m; i++) {printf("%6s%2d:", "物理块", i + 1);for (int j = 0; j < n; j++)printf("%3c ", ans[i][j].page);printf("\n");}printf("%8s:", "缺页中断");int ruptsum = 0;for (int r = 0; r < n; r++) {printf("%3c ", pra[r].interrupt);if (pra[r].interrupt == '*')ruptsum++;}printf("\n缺页率:%.2f%%\n\n", ruptsum * 100.0 / n);
}
int main() {int ca = 0;while (1) {printf("请输入要进行的操作\n");printf("1、先进先出算法\n");printf("2、最近最少使用页面淘汰算法\n");printf("0、退出程序\n");printf("\n");scanf("%d", &ca);if (ca < 1 || ca > 2)break;pageReplace(ca);}return 0;
}