一、实验目的
通过编写程序模拟虚拟内存页面置换算法,包括先进先出(FIFO)、最佳置换(OPT)和最近最久未使用(LRU),加深对虚拟内存管理和页面置换概念的理解,掌握这三种页面置换算法的工作原理和实现方式。通过程序模拟不同算法的页面访问过程,分析其缺页次数和缺页率,提升针对页面交换问题的解决能力。
二、需求分析
2.1 程序设计任务
本程序旨在模拟FIFO、OPT和LRU三种页面置换算法,以解决在进程执行过程中由于页面访问而发生的缺页问题。程序能够通过输入页面访问序列和物理块数,演示每种算法如何进行页面置换,并计算每种算法的缺页次数和缺页率,最终帮助用户了解不同页面置换策略在内存管理中的表现差异。
2.2 输入内容、形式及范围
1. 物理块数(BlockNum):
输入内容:正整数,表示分配给每个进程的物理块数量。
输入形式:用户通过键盘输入单一正整数。
输入范围:1 ≤ BlockNum ≤ 100。
- 页面数量(PageNum):
输入内容:正整数,表示进程访问的页面数量。
输入形式:用户通过键盘输入单一正整数。
输入范围:1 ≤ PageNum ≤ 100。
- 页面访问序列(PageOrder):
输入内容:一组正整数,表示进程访问的页面编号序列。
输入形式:用户通过键盘输入正整数序列,长度为 PageNum。
- 算法选择(choice):
输入内容:表示选择的页面置换算法。
输入形式:用户通过键盘输入单一正整数(1-FIFO, 2-OPT, 3-LRU)。
输入范围:1 ≤ choice ≤ 3。
2.3 输出形式
- 页面置换过程:
输出内容:每次访问页面时内存中的页面分配情况。
输出形式:按行显示每次页面访问时的内存状态,包括已分配的页面或者空白标识。
- 缺页结果:
输出内容:计算得出的缺页次数和缺页率。
输出形式:显示缺页次数和缺页率。
2.4 测试数据
2.4.1 正确的输入
2.4.2 错误的输入
- 物理块数超过100:
- 页面数量超过100:
3.算法选择不在1到3间:
三、概要设计
3.1 抽象数据类型的定义
1. struct PageReplacementAlgorithm 数据类型
用于表示页面置换算法中的核心数据结构及属性,包括:
- PageOrder[MaxNumber]: 页面访问序列,记录进程访问的页面编号。
- Simulate[MaxNumber][MaxNumber]: 模拟过程矩阵,用于记录每次页面访问时的内存状态。
- PageCount[MaxNumber]: 当前在内存中的页面,用于存储被加载到内存的页面。
- LackNum: 记录缺页次数。
- LackPageRate: 计算缺页率 (缺页次数/P 页面的总数)。
- PageNum: 页面数量,表示访问的页面个数。
- BlockNum: 物理块数,表示可用的物理内存块数量。
- found: 布尔标志,记录当前页是否已在内存中。
2.辅助变量及数据结构
- current: 当前替换位置,用于算法如 FIFO 和 LRU 的页面置换。
- time[MaxNumber]: 记录每个页面的最后使用时间,适用于 LRU 算法。
- maxFutureUse[MaxNumber]: 用于存储每个页面下次使用的位置,适用于 OPT 算法。
3.2 主程序流程
1.输入阶段:
用户输入:
输入物理块数(BlockNum),表示内存中可用的页面块数。
输入页面数量(PageNum),表示将要访问的页面个数。
输入页面访问序列(PageOrder),表示每次访问的页面编号序列。
数据校验:
确保输入的页数和物理块数符合逻辑限制,例如 BlockNum 和 PageNum 的范围不得超过最大值 (MaxNumber) 并且需为正整数。
2.算法处理阶段:
根据选择的页面置换算法(FIFO, OPT, LRU)进行处理:
FIFO:循环遍历页面访问序列,按照先进先出的顺序替换页。
OPT:找出在未来最长时间不会被引用的页面进行替换。
LRU:记录每个页面的最后使用时间,替换最久未使用的页面。
缺页处理:
每次访问页面时检查该页面是否已在内存中,如果未在内存中,则进行缺页替换并更新缺页数量及状态矩阵。
3.结果输出阶段:
输出每次页面访问的内存状态,显示哪些页面被加载到内存中。
输出缺页次数和计算出的缺页率。
四、详细设计
4.1 FIFO 实现
使用循环变量 current 指向需要替换的页面位置,初始为 0。
对于每次页面访问:
- 检查页面是否已在内存中:遍历 PageCount 判断是否命中。
- 若页面未命中(缺页),按照先进先出原则替换内存中的页面,替换位置由 current 决定,随后将 current 指向下一位置((current + 1) % BlockNum)。
- 对每次访问记录当前内存状态到 Simulate 矩阵。
4.2 OPT 实现
对于每次页面访问:
- 检查页面是否已在内存中:遍历 PageCount 判断是否命中。
- 若页面未命中(缺页):
如果内存未满,直接插入新页面到首个空闲位置。
如果内存已满:
- 模拟未来的页面访问序列,找出在未来最久不会被使用的页面作为替换目标。
- 替换选中的页面,记录缺页次数并将新页面放入内存。
- 对每次访问记录当前内存状态到 Simulate 矩阵。
4.3 LRU实现
使用数组 time 记录每个页面的最近使用时间,初始值为 -1。
对于每次页面访问:
- 检查页面是否已在内存中:遍历 PageCount 判断是否命中,并更新该页面的最近使用时间为当前时间索引 i。
- 若页面未命中(缺页):
如果内存未满,直接插入新页面到首个空闲位置,同时更新最近时间。
如果内存已满:
- 查找最近使用时间最小的页面(最久未使用)并替换,将新页面插入替换位置并更新时间。
- 对每次访问记录当前内存状态到 Simulate 矩阵。
五、调试分析
5.1 调试过程中遇到的问题及解决方案
问题 : LRU算法在BlockNum内如果页面有重复,模拟矩阵输出会不正确。
解决方案: 加current来计数。
5.2 算法的时空复杂度分析
5.2.1 算法时间复杂度分析
(1) FIFO(先进先出算法)
每次页面访问需要遍历当前内存页面 BlockNum 次以检查页面是否命中,此外替换时直接访问 current 指针位置,无需额外开销。
总体时间复杂度为 O(PageNum * BlockNum)。
- OPT(最优置换算法)
每次页面访问需要模拟未来访问序列,对于每个内存页面,需遍历未来序列以查找最近的使用时间或确认是否不再使用。
总体时间复杂度为 O(PageNum * BlockNum * (PageNum - i)),其中 i 是当前页面访问的索引,这通常可以简化为 O(PageNum² * BlockNum)。
- LRU(最近最少使用算法)
每次页面访问需要遍历当前内存页面 BlockNum 次以检查是否命中,若缺页时还需遍历最近时间数组 time 查找最近未被使用的页面。
总体时间复杂度为 O(PageNum * BlockNum)。
5.2.2 空间复杂度分析
所有算法需存储页面访问序列(PageOrder,O(PageNum))和内存块状态(PageCount,O(BlockNum))。
FIFO 算法需要一个替换位置指针 current (O(1))。
LRU 算法需记录最近使用时间数组 time (O(BlockNum))。
OPT 算法需存储未来页面预取的位置数组 (O(PageNum))。
5.2.3 改进设想
1. 优化 LRU 的效率:
使用先进数据结构(如优先队列或哈希表+链表)来记录页面的使用时间,降低查找和更新最近使用页面的时间开销,从而提升算法运行效率。
2.简化 OPT 的未来访问模拟:
通过构建一个逆序索引表(记录每个页面编号在后续访问序列中的最近访问位置),减少重复计算的次数,从而显著降低未来访问序列的复杂度。
3.实验动态调整内存块数:
在实际运行过程中,根据当前缺页情况动态调整 BlockNum 的大小,模拟更接近操作系统的自适应页面替换机制,从而更真实地反映算法在实际环境中的性能。
5.3 经验和体会
通过本次实验,深入理解了页面置换算法在内存管理中的核心作用,对 FIFO、OPT 和 LRU 算法的特点有了更深刻的认识。在设计和调试代码的过程中,更理解了算法严谨性的重要性。此外,通过分析不同算法的优缺点,体会到理论上最优的算法(如 OPT)虽然性能优异,但因复杂性较高,实际应用中更倾向采用近似优化的简单方法(如 LRU)。此次实验还让我意识到输入处理和错误校验在程序开发中的重要性,一个健壮的程序需要在确保核心算法正确的同时做好用户交互部分的设计。
六、用户使用说明
1.编译运行
使用编译器(如 GCC 或 Visual Studio)编译程序,生成可执行文件后运行程序。
2.输入参数
按提示输入以下参数:
- 物理块数 BlockNum:系统内存中可用页面块的数量。
- 页面数量 PageNum:页面访问序列中的总页面数量。
- 页面访问序列 PageOrder:用户逐次输入一组页面编号,以完成整个访问序列的录入。
选择算法:
- 输入对应的编号(1.FIFO, 2.OPT, 3.LRU)选择需要测试的页面置换算法。
3.运行与资源请求
程序会模拟指定算法的页面置换过程,并在每一次页面访问时,输出当前内存中的页面状态、缺页情况以及被替换页面(若有)。
4.查看结果
程序会在实验完成后提供以下数据:
- 各种资源请求下的页面命中情况。
- 总缺页次数(LackNum)和缺页率(LackPageRate)。
5.多轮实验
用户可选择多次运行不同算法,以便对比它们的性能差异,观察页面缺页率在不同访问序列中的表现。输入 Y 继续运行或 N 退出程序。
七、测试结果
1.输入
2.FIFO算法
3.OPT算法
4.LRU算法
#include <iostream>
#include <iomanip>
using namespace std;
const int MaxNumber = 100;
int PageOrder[MaxNumber]; // 页面访问序列
int Simulate[MaxNumber][MaxNumber]; // 模拟过程矩阵
int PageCount[MaxNumber]; // 内存中的页面
int PageNum; // 页面数量
int BlockNum; // 物理块数
int LackNum; // 缺页次数
double LackPageRate; // 缺页率
bool found;
// FIFO算法
void FIFO() {
int current = 0; // 当前替换位置
for (int i = 0; i < PageNum; i++) {
found = false;
// 检查是否已在内存中
for (int j = 0; j < BlockNum; j++) {
if (PageCount[j] == PageOrder[i]) {
found = true;
break;
}
}
// 缺页处理
if (!found) {
PageCount[current] = PageOrder[i];
current = (current + 1) % BlockNum;
LackNum++;
}
// 记录当前状态
for (int j = 0; j < BlockNum; j++) {
Simulate[i][j] = PageCount[j];
}
}
}
// OPT算法
void OPT() {
for (int i = 0; i < PageNum; i++) {
found = false;
// 检查是否已在内存中
for (int j = 0; j < BlockNum; j++) {
if (PageCount[j] == PageOrder[i]) {
found = true;
break;
}
}
if (!found) {
// 如果还有空闲物理块
if (i < BlockNum) {
PageCount[i] = PageOrder[i];
}
else {
// 找出最长时间不会被使用的页面
int max = -1;
int pos = 0;
for (int j = 0; j < BlockNum; j++) {
int k;
for (k = i + 1; k < PageNum; k++) {
if (PageCount[j] == PageOrder[k]) break;
}
if (k > max) {
max = k;
pos = j;
}
}
PageCount[pos] = PageOrder[i];
}
LackNum++;
}
// 记录当前状态
for (int j = 0; j < BlockNum; j++) {
Simulate[i][j] = PageCount[j];
}
}
}
// LRU算法
void LRU() {
int time[MaxNumber]; // 记录页面最后使用时间
int current = 0;
for (int i = 0; i < BlockNum; i++)
time[i] = -1; // 初始化时间记录
for (int i = 0; i < PageNum; i++) {
found = false;
// 检查是否已在内存中
for (int j = 0; j < BlockNum; j++) {
if (PageCount[j] == PageOrder[i]) {
found = true;
time[j] = i; // 更新页面的最后使用时间
break;
}
}
if (!found) {
if (current < BlockNum) {// 如果还未满
PageCount[current++] = PageOrder[i];
time[i] = i;
}
else {
// 找出最久未使用的页面
int minTime = i, pos = 0;
for (int j = 0; j < BlockNum; j++) {
if (time[j] < minTime) {
minTime = time[j];
pos = j;
}
}
PageCount[pos] = PageOrder[i];
time[pos] = i;
}
LackNum++;
}
// 记录当前状态
for (int j = 0; j < BlockNum; j++) {
Simulate[i][j] = PageCount[j];
}
}
}
// 显示页面置换的结果
void showResult() {
cout << "\n页面置换过程:" << endl;
for (int i = 0; i < PageNum; i++) {
cout << "访问页面 " << PageOrder[i] << ": ";
for (int j = 0; j < BlockNum; j++) {
if (Simulate[i][j] == -1)
cout << "□ ";
else
cout << Simulate[i][j] << " ";
}
cout << endl;
}
LackPageRate = (double)LackNum / PageNum;
cout << "\n缺页次数: " << LackNum << endl;
cout << "缺页率: " << fixed << setprecision(2) << LackPageRate << endl;
}
// 主函数
int main() {
int choice;
cout << "请输入物理块数: ";
cin >> BlockNum;
if (BlockNum > MaxNumber) {
cout << "物理块数过多!";
return 1;
}
cout << "请输入页面数量: ";
cin >> PageNum;
if (PageNum > MaxNumber) {
cout << "页面数过多!";
return 1;
}
cout << "请输入页面访问序列: ";
for (int i = 0; i < PageNum; i++) {
cin >> PageOrder[i];
}
// 初始化模拟矩阵和页面计数器
for (int i = 0; i < MaxNumber; i++) {
for (int j = 0; j < MaxNumber; j++) {
Simulate[i][j] = -1;
}
PageCount[i] = -1;
}
LackNum = 0;
LackPageRate = 0.0;
cout << "\n选择页面置换算法:" << endl;
cout << "1. FIFO" << endl;
cout << "2. OPT" << endl;
cout << "3. LRU" << endl;
cout << "请选择(1-3): ";
cin >> choice;
switch (choice) {
case 1:
FIFO();
break;
case 2:
OPT();
break;
case 3:
LRU();
break;
default:
cout << "无效选择!" << endl;
return 1;
}
showResult();
return 0;
}