存储管理的主要功能之一是合理地分配空间。请求页式管理是一种常用的虚拟存储管理技术。通过请求页式存储管理中页面置换算法模拟设计,了解虚拟存储技术的特点,掌握请求页式存储管理的页面置换算法。
设计内容要求:计算并输出下述各种算法在内存容量为3块、4块下的缺页率。
- 先进先出的算法(FIFO)。 要求用数组或链表方法实现
- 最近最少使用算法(LRU)。 要求用计数器或堆栈方法实现
一、C++
C++实现的思想
C++代码实现了两个页面置换算法:FIFO和LRU。页面置换算法是操作系统中用于管理虚拟内存的关键组成部分。这段代码的主要作用是模拟这两种页面置换算法在给定的页面请求序列下的运行情况,并以表格形式动态展示算法的执行过程。
页面置换算法的基类定义了一个纯虚函数,该函数用于模拟页面置换算法。基类的目的是为了提供一个通用的接口,以便从该接口派生出不同的页面置换算法类。FIFO 类和 LRU 类分别派生自基类。每个派生类都实现了simulate函数,该函数用于模拟相应的页面置换算法。每个派生类都维护了算法执行过程中的数据结构,如队列或列表,以及用于快速查找页面是否在内存中的哈希集合。我的main函数负责整个程序的控制流。它获取用户输入的内存块数量和选择的页面置换算法。根据用户的选择,创建相应的页面置换算法对象,这里可以是FIFO或LRU。
页面置换算法逻辑:根据页面请求序列,判断页面是否在内存中。如果页面不在内存中,发生缺页。根据算法策略进行页面置换或将新页面加入内存。更新内存状态和页面置换信息,并动态展示在表格中。计算并输出缺页率。FIFO算法是在页面置换时,选择最早进入内存的页面进行替换。使用一个队列来维护内存中的页面顺序,新页面加入队尾,替换时移出队头。LRU算法是在页面置换时,选择最长时间未被访问的页面进行替换。使用一个列表来维护内存中页面的访问顺序,新页面加入列表头,每次访问的页面移到列表头,替换时选择列表尾的页面。
代码细节展示
(1)包含头文件和命名空间
#include <iostream> // 输入输出流 #include <vector> // 动态数组 #include <unordered_set>// 无序集合 #include <list> // 双向链表 #include <iomanip> // 控制输出格式 #include <chrono> // 时间库,用于模拟动态效果 #include <thread> // 多线程库,用于模拟动态效果 #include <sstream> // 字符串流,用于构造输出信息 using namespace std;
这一部分包含了程序所需的头文件,其中每个头文件提供了不同的功能,例如vector用于动态数组,unordered_set用于快速查找,list用于双向链表等。
(2)页面置换算法基类
class PageReplacementAlgorithm { public: // 纯虚函数,用于模拟页面置换算法 virtual void simulate(const vector<int>& pageRequests, int capacity) = 0; };
定义了一个页面置换算法的基类,其中包含一个纯虚函数simulate,该函数负责模拟页面置换算法的过程。
(3)FIFO页面置换算法实现
class FIFO : public PageReplacementAlgorithm { public: void simulate(const vector<int>& pageRequests, int capacity) override { list<int> fifoQueue; // 用于存储页面的FIFO队列 unordered_set<int> pageSet; // 用于快速查找页面是否在内存中 int pageFaults = 0; // 记录缺页次数 // 用于保存表格数据 vector<vector<string>> table; // 添加表头 table.push_back({ "Target", "Page", "Memory", "Page Fault", "Status" }); for (int i = 0; i < pageRequests.size(); ++i) { int page = pageRequests[i]; bool pageInMemory = pageSet.find(page) != pageSet.end(); vector<string> row; // 添加当前目标页面 row.push_back("page " + to_string(page)); if (!pageInMemory) { // 页面不在内存中,发生缺页 pageFaults++; if (fifoQueue.size() == capacity) { // 如果内存已满,进行页面置换 int removedPage = fifoQueue.front(); fifoQueue.pop_front(); pageSet.erase(removedPage); row.push_back("page " + to_string(removedPage) + " out"); } else { // 如果内存未满,不进行页面置换 row.push_back(" "); } // 将新页面加入内存和FIFO队列 fifoQueue.push_back(page); pageSet.insert(page); row.push_back("page " + to_string(page) + " in"); } else { // 页面已在内存中 row.push_back(" "); row.push_back("page " + to_string(page) + " hit"); } // 将当前内存状态添加到表格中 string memoryStatus; for (const auto& p : fifoQueue) { memoryStatus += "page " + to_string(p) + " "; } // 添加空白占位 for (int j = fifoQueue.size(); j < capacity; ++j) { memoryStatus += " "; } row.push_back(memoryStatus); // 添加缺页或不缺页的标记 row.push_back(pageInMemory ? " √" : " ×"); // 将当前行添加到表格中 table.push_back(row); // 输出整个表格 system("cls"); displayTable(table); // 等待一段时间,模拟动态效果 this_thread::sleep_for(chrono::milliseconds(500)); } // 计算并输出缺页率 double pageFaultRate = static_cast<double>(pageFaults) / pageRequests.size(); cout << "\n缺页率: " << fixed << setprecision(2) << pageFaultRate * 100 << "%" << endl; } private: // 用于显示表格 void displayTable(const vector<vector<string>>& table) { // 确定每列的最大宽度 vector<int> colWidths(table[0].size(), 0); for (const auto& row : table) { for (size_t i = 0; i < row.size(); ++i) { colWidths[i] = max(colWidths[i], static_cast<int>(row[i].length())); } } // 输出整个表格 for (const auto& row : table) { for (size_t i = 0; i < row.size(); ++i) { cout << setw(colWidths[i] + 2) << left << "|" + row[i]; } cout << "|" << endl; // 输出分隔线 for (size_t i = 0; i < row.size(); ++i) { cout << "+" << setw(colWidths[i] + 1) << setfill('-') << left; } cout << "+" << setfill(' ') << endl; } } };
这一部分定义了FIFO页面置换算法的具体实现。simulate函数模拟了页面置换的过程,包括缺页、页面置换等情况,并通过表格展示每步的操作。displayTable函数用于显示表格,确保格式整齐。
(4)LRU页面置换算法实现
class LRU : public PageReplacementAlgorithm { public: void simulate(const vector<int>& pageRequests, int capacity) override { list<int> lruList; // 用于存储页面的LRU列表 unordered_set<int> pageSet; // 用于快速查找页面是否在内存中 int pageFaults = 0; // 记录缺页次数 // 用于保存表格数据 vector<vector<string>> table; // 添加表头 table.push_back({ "Target", "Page", "Memory", "Page Fault", "Status" }); for (int i = 0; i < pageRequests.size(); ++i) { int page = pageRequests[i]; bool pageInMemory = pageSet.find(page) != pageSet.end(); vector<string> row; // 添加当前目标页面 row.push_back("page " + to_string(page)); if (!pageInMemory) { // 页面不在内存中,发生缺页 pageFaults++; if (lruList.size() == capacity) { // 如果内存已满,进行页面置换 int removedPage = lruList.back(); lruList.pop_back(); pageSet.erase(removedPage); row.push_back("page " + to_string(removedPage) + " out"); } else { // 如果内存未满,不进行页面置换 row.push_back(" "); } // 将新页面加入内存和LRU列表 lruList.push_front(page); pageSet.insert(page); row.push_back("page " + to_string(page) + " in"); } else { // 页面已在内存中,更新LRU列表 lruList.remove(page); lruList.push_front(page); row.push_back(" "); row.push_back("page " + to_string(page) + " hit"); } // 将当前内存状态添加到表格中 string memoryStatus; for (const auto& p : lruList) { memoryStatus += "page " + to_string(p) + " "; } // 添加空白占位 for (int j = lruList.size(); j < capacity; ++j) { memoryStatus += " "; } row.push_back(memoryStatus); // 添加缺页或不缺页的标记 row.push_back(pageInMemory ? " √" : " ×"); // 将当前行添加到表格中 table.push_back(row); // 输出整个表格 system("cls"); displayTable(table); // 等待一段时间,模拟动态效果 this_thread::sleep_for(chrono::milliseconds(500)); } // 计算并输出缺页率 double pageFaultRate = static_cast<double>(pageFaults) / pageRequests.size(); cout << "\n缺页率: " << fixed << setprecision(2) << pageFaultRate * 100 << "%" << endl; } private: // 用于显示表格 void displayTable(const vector<vector<string>>& table) { // 确定每列的最大宽度 vector<int> colWidths(table[0].size(), 0); for (const auto& row : table) { for (size_t i = 0; i < row.size(); ++i) { colWidths[i] = max(colWidths[i], static_cast<int>(row[i].length())); } } // 输出整个表格 for (const auto& row : table) { for (size_t i = 0; i < row.size(); ++i) { cout << setw(colWidths[i] + 2) << left << "|" + row[i]; } cout << "|" << endl; // 输出分隔线 for (size_t i = 0; i < row.size(); ++i) { cout << "+" << setw(colWidths[i] + 1) << setfill('-') << left; } cout << "+" << setfill(' ') << endl; } } };
这一部分定义了LRU页面置换算法的具体实现,逻辑和FIFO类似。simulate函数模拟了LRU算法的操作过程,并通过表格展示每步的操作。displayTable函数用于显示表格,确保格式整齐。
(5)主函数
int main() { // 获取用户输入的内存块数量 int memoryCapacity; cout << "输入内存块的数量: "; cin >> memoryCapacity; cout << "选择页面置换算法:" << endl; cout << "1. FIFO" << endl; cout << "2. LRU" << endl; int choice; cin >> choice; PageReplacementAlgorithm* algorithm; if (choice == 1) { algorithm = new FIFO(); } else if (choice == 2) { algorithm = new LRU(); } else { cout << "无效选择,退出程序." << endl; return 1; } // 示例页面请求序列 vector<int> pageRequests = { 7, 0, 1, 2, 0, 3, 0, 4, 2, 3, 0, 3, 2, 1, 2, 0, 1, 7, 0, 1 }; // 运行选择的页面置换算法 algorithm->simulate(pageRequests, memoryCapacity); // 释放动态分配的内存 delete algorithm; return 0; }
这一部分包含了主函数,用户可以选择FIFO或LRU算法,并运行示例页面请求序列的页面置换模拟。程序运行后将输出每步的操作和缺页率。
结果展示
①内存容量为3块时,FIFO算法
②内存容量为4块时,FIFO算法
③内存容量为3块时,LRU算法
④内存容量为4块时,LRU算法
二、Java
Java实现的思想
Java代码实现了两个页面置换算法:FIFO(先进先出)和LRU(最近最少使用)。页面置换算法是操作系统中虚拟内存管理的核心组成部分,而这段代码旨在模拟这两种算法在给定的页面请求序列下的执行过程,并通过表格形式动态展示算法的操作。
首先,定义了一个名为PageReplacementAlgorithm的接口,其中包含一个名为simulate的抽象方法。这个接口的目的是提供一个通用的框架,以便从这个接口派生出不同的页面置换算法类。接口中的simulate方法规定了页面置换算法需要实现的行为,具体的算法实现会在派生类中完成。
接着,代码实现了两个具体的页面置换算法类:FIFO和LRU,它们都实现了PageReplacementAlgorithm接口。每个算法类中都包含一个simulate方法,用于模拟相应算法的执行过程。
在main函数中,用户首先被提示输入内存块的数量和选择的页面置换算法(1代表FIFO,2代表LRU)。然后,根据用户的选择,创建相应的页面置换算法对象。无效的选择会导致程序退出。接下来,程序使用一个预定义的页面请求序列(pageRequests)来运行所选的页面置换算法。
对于FIFO算法,内存维护了一个队列(memoryQueue),新页面被加入队尾,发生缺页时队头的页面被替换。每步操作的细节和内存状态都会被动态展示在表格中,通过等待一段时间来模拟动态效果。计算并输出缺页率。
对于LRU算法,内存维护了一个列表(lruList)和一个哈希集合(pageSet)。新页面被加入列表头,发生缺页时选择列表尾的页面进行替换。每次访问的页面会被移动到列表头。同样,每步操作的详细信息和内存状态通过表格展示,并计算并输出缺页率。
整个代码体现了面向对象的设计原则,通过接口和多态性实现了通用的页面置换算法框架,并通过具体的算法类进行了实现。同时,通过表格形式的动态展示,使得算法执行过程更加直观,方便理解和分析。
代码细节展示
(1)主类
import java.util.List;
import java.util.Scanner;
class main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 获取用户输入的内存块数量
System.out.print("输入内存块的数量: ");
int memoryCapacity = scanner.nextInt();
// 获取用户选择的算法
System.out.println("选择页面置换算法:");
System.out.println("1. FIFO");
System.out.println("2. LRU");
int algorithmChoice = scanner.nextInt();
List<Integer> pageRequests = List.of(7, 0, 1, 2, 0, 3, 0, 4, 2, 3, 0, 3, 2, 1, 2, 0, 1, 7, 0, 1);
// 根据用户选择调用相应算法
PageReplacementAlgorithm algorithm;
if (algorithmChoice == 1) {
algorithm = new FIFO();
} else if (algorithmChoice == 2) {
algorithm = new LRU();
} else {
System.out.println("无效选择,退出程序.");
return;
}
// 运行算法模拟
algorithm.simulate(pageRequests, memoryCapacity);
}
}
这一部分是程序的入口,通过Scanner获取用户输入的内存块数量和选择的页面置换算法(FIFO或LRU),然后调用相应的算法进行模拟。
(2)页面置换算法接口
import java.util.List;
public interface PageReplacementAlgorithm {
void simulate(List<Integer> pageRequests, int capacity);
}
这是一个接口,规定了页面置换算法需要实现的方法 simulate
,用于模拟页面置换算法的过程。
(3)FIFO算法实现
import java.util.*;
class FIFO implements PageReplacementAlgorithm {
@Override
public void simulate(List<Integer> pageRequests, int capacity) {
Queue<Integer> memoryQueue = new LinkedList<>();
int pageFaults = 0;
// 添加表头
System.out.println(String.format("| %-10s | %-10s | %-20s | %-20s | %-10s |", "Target", "Page", "Memory", "Page Fault", "Status"));
System.out.println("+------------+------------+----------------------+----------------------+------------+");
for (int i = 0; i < pageRequests.size(); ++i) {
int page = pageRequests.get(i);
boolean pageInMemory = memoryQueue.contains(page);
List<String> row = new ArrayList<>();
// 添加当前目标页面
row.add("Page " + page);
if (!pageInMemory) {
// 页面不在内存中,发生缺页
pageFaults++;
// 如果内存已满,进行页面置换
if (memoryQueue.size() == capacity) {
int removedPage = memoryQueue.poll();
row.add("Page " + removedPage + " out");
} else {
row.add(" ");
}
// 将新页面加入内存
memoryQueue.offer(page);
row.add("Page " + page + " in");
} else {
// 页面已在内存中
row.add(" ");
row.add("Page " + page + " hit");
}
// 将当前内存状态添加到表格中
StringBuilder memoryStatus = new StringBuilder();
for (int p : memoryQueue) {
memoryStatus.append("Page ").append(p).append(" ");
}
// 添加空白占位
for (int j = memoryQueue.size(); j < capacity; ++j) {
memoryStatus.append(" ");
}
row.add(memoryStatus.toString());
// 添加缺页或不缺页的标记
row.add(pageInMemory ? " √" : " ×");
// 输出当前行
System.out.println(String.format("| %-10s | %-10s | %-20s | %-20s | %-10s |", row.toArray()));
// 等待一段时间,模拟动态效果
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 计算并输出缺页率
double pageFaultRate = (double) pageFaults / pageRequests.size();
System.out.printf("\nPage Fault Rate: %.2f%%\n", pageFaultRate * 100);
}
}
这一部分是FIFO算法的具体实现。simulate方法模拟了FIFO算法的操作过程,包括缺页、页面置换等情况,并通过表格展示每步的操作。
(4)LRU算法实现
import java.util.*;
class LRU implements PageReplacementAlgorithm {
@Override
public void simulate(List<Integer> pageRequests, int capacity) {
LinkedList<Integer> lruList = new LinkedList<>();
Set<Integer> pageSet = new HashSet<>();
int pageFaults = 0;
// 添加表头
System.out.println(String.format("| %-10s | %-10s | %-20s | %-20s | %-10s |", "Target", "Page", "Memory", "Page Fault", "Status"));
System.out.println("+------------+------------+----------------------+----------------------+------------+");
for (int i = 0; i < pageRequests.size(); ++i) {
int page = pageRequests.get(i);
boolean pageInMemory = pageSet.contains(page);
List<String> row = new ArrayList<>();
// 添加当前目标页面
row.add("Page " + page);
if (!pageInMemory) {
// 页面不在内存中,发生缺页
pageFaults++;
// 如果内存已满,进行页面置换
if (lruList.size() == capacity) {
int removedPage = lruList.removeLast();
pageSet.remove(removedPage);
row.add("Page " + removedPage + " out");
} else {
row.add(" ");
}
// 将新页面加入内存和LRU列表
lruList.addFirst(page);
pageSet.add(page);
row.add("Page " + page + " in");
} else {
// 页面已在内存中,更新LRU列表
lruList.remove((Integer) page);
lruList.addFirst(page);
row.add(" ");
row.add("Page " + page + " hit");
}
// 将当前内存状态添加到表格中
StringBuilder memoryStatus = new StringBuilder();
for (int p : lruList) {
memoryStatus.append("Page ").append(p).append(" ");
}
// 添加空白占位
for (int j = lruList.size(); j < capacity; ++j) {
memoryStatus.append(" ");
}
row.add(memoryStatus.toString());
// 添加缺页或不缺页的标记
row.add(pageInMemory ? " √" : " ×");
// 输出当前行
System.out.println(String.format("| %-10s | %-10s | %-20s | %-20s | %-10s |", row.toArray()));
// 等待一段时间,模拟动态效果
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 计算并输出缺页率
double pageFaultRate = (double) pageFaults / pageRequests.size();
System.out.printf("\nPage Fault Rate: %.2f%%\n", pageFaultRate * 100);
}
}
这一部分是LRU算法的具体实现,逻辑和FIFO类似。simulate方法模拟了LRU算法的操作过程,并通过表格展示每步的操作。
结果展示
①内存容量为3块时,FIFO算法
②内存容量为4块时,FIFO算法
③内存容量为3块时,LRU算法
④内存容量为4块时,LRU算法
三、设计感悟
在完成这个页面置换算法的设计中。
初始阶段,我思考了如何设计一个灵活、可扩展的系统。通过引入基类和接口的概念,我创造了一个通用的页面置换算法接口,这使得我能够以统一的方式调用不同的算法。这种抽象设计不仅提高了代码的可维护性,还为未来添加新算法提供了便利。
在实现FIFO和LRU算法时,我深入思考了不同的数据结构选择对算法性能的影响。使用队列和链表的决策是基于它们在页面置换场景中的优越性能。这强调了在算法设计中正确选择数据结构的重要性。
为了更生动地展示算法的执行过程,我决定在每一步都动态显示当前内存状态和执行情况。这牵涉到在控制台上输出表格形式的数据,包括清屏和等待一段时间的操作。这一部分让我更深入地了解了C++和Java在控制台操作方面的技术,如清屏命令、格式化输出等。
主程序需要与用户交互,获取内存块数量和选择的算法。为了提高用户体验,我添加了一些输入检查,确保用户输入的是合法的数字。对于无效的输入,通过输出相应的提示信息,我保证程序具有更好的稳健性。
在Java版本中,为了实现动态显示的效果,我使用了Thread类的一个静态方法使当前线程休眠,进入阻塞状态来使程序暂停一段时间。然而这可能引发当阻塞方法收到中断请求的时候就会抛出InterruptedException异常。通过仔细处理这个异常,我确保了程序在可能的中断情况下的正常执行,这在线程等待方面有所技术知识的需求。
总的来说,这个设计综合了页面置换算法的核心原理和实现方式还有抽象设计、数据结构选择、动态显示以及用户输入和异常处理等方面。