目录
1. 通过随机数产生一个指令序列,共320条指令。指令的地址按下述原则生成:
前言
存储管理的主要功能之一是合理地分配空间。请求页式管理是一种常用的虚拟存储管理技术。
本实验的目的是通过请求页式存储管理中页面置换算法模拟设计,了解虚拟存储技术的技术特点,掌握请求页式存储管理的页面置换算法。
实现:先进先出的算法(FIFO)、最近最久未使用算法(LRU)、最近最少访问页面算法(LFR)、最近未用过算法(NUR)、最佳页面置换算法(OPT)
实验要求
1. 通过随机数产生一个指令序列,共320条指令。指令的地址按下述原则生成:
①50%的指令是顺序执行的;
②50%的指令是均匀分布在前地址部分;
③50%的指令是均匀分布在后地址部分。
具体的实施方法是:
①在 [0,319] 的指令之间随即选取一起点m;
②顺序执行一条指令,即执行地址为m+1的指令;
③在前地址[0,m+1]中随机选取一条指令并执行,该指令的地址为m′;
④顺序执行一条指令,其地址为 m′+ 1;
⑤在后地址[m′+ 2,319]中同上操作;
⑥重复上述步骤①-⑤,直到执行 n 次指令。
2. 将指令序列映射为页地址流
设:①页面大小为1k;
②用户内存容量为4页到32页;
③用户虚存容量为32k。
在用户虚存中,按每k存放10条指令排在虚存地址,即320条指令在虚存中的存放方式为:
第0条-第9条指令为第0页(对应虚存地址为[0,9]);
第10条-第19条指令为第一页(对应虚存地址为[10,19]);
… …
第310条~第319条指令为第31页(对应虚地址为[310,319])。
按以上方式,用户指令可组成32页。
代码部分
前置部分
使用到的头文件和宏定义,后续部分直接使用(因为接近底层,没有使用vector等stl容器)
#include <assert.h>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#define INC_NUM 320
#define RUN_NUM 320
#define _1K_ 10
指令序列生成
这部分没有难度,按照要求来就行,值得注意的是,可以设置随机数种子为进程ip或者当前时间,否则每一次随机数将相同。重载了'[]' '<<' 为后续操作提供便利。
class IncSeq
{
IncSeq(IncSeq&) = delete;
IncSeq& operator=(IncSeq&) = delete;
public:
IncSeq(int num) : size(num), data(new int[num])
{
for (int i = 0; i < num; i++)
{
switch (i % 6)
{
case 0: // 0 ~ 319
data[i] = rand() % 320;
break;
case 2: // 前地址随机取
data[i] = rand() % data[i - 1];
break;
case 4: // 后地址随机取
data[i] = data[i - 1] + rand() % (num - data[i - 1]);
break;
case 1: // 顺序取下一指令
case 3:
case 5:
data[i] = (data[i - 1] + 1) % num;
}
}
}
~IncSeq() { delete[] data; }
int Size() const { return this->size; }
int operator[](int num) const
{
assert(num < size);
return data[num];
}
friend std::ostream& operator<<(std::ostream& os, const IncSeq& obj);
private:
int* data;
int size;
};
std::ostream& operator<<(std::ostream& os, const IncSeq& obj)
{
os << obj[0];
for (int i = 1; i < obj.Size(); i++)
{
os << " " << obj[i];
}
return os;
}
核心部分
思路
首先是关于为什么要使用c++而不使用c语言:
1. 除了opt,其他四种算法实现思路基本相同,使用多态指针可以更好的认知四种算法的相同之处和不同之处。
2. 交互部分有大量的信息输出,单纯使用常规使用c语言的方式会导致代码冗余,重复部分占比较大,使用c++多态指针,虚函数,重载输出流可以一定程度缓解这个问题。
3. 通过类的析构函数可以更好的管理内存,避免迭代开空间,迭代释放空间,在代码量较大的情况减少内存泄漏的可能。
模块设计思路:
基类
只需要对外公开关于run部分接口,后面发现需要求命中率,增加了一个HitRate接口。
因为页面置换算法的核心是置换,所以信息的迭代很重要,在基类中存储了前一次执行指令后的相关信息:是否置换,哪里置换,置换前是什么(置换后可以查询得到),还有必须的就是页面的存储空间,
很重要的一个虚函数就是关于信息输出的,这部分关系着代码的易用性,后面再详细展开。
后面增加了两个计数器,总次数和缺页次数用于计算命中率。
子类(算法类):
核心是实现父类的两个接口:日志和交互,至于具体的实现细节,辅助变量等就根据算法不同子类自行安排。
这其中差别比较大的就是opt,因为opt是基于未来的算法,所以需要传入整个指令序列,用于算法实现。
主函数:
主函数便是生成指令序列,通过键盘输入确定多态指针绑定哪一个算法,利用面向对象晚绑定的特性,让代码非常灵活,增强扩展性,减少代码冗余:五个不同的算法,c语言需要五个不同的模块,但是其中很多交互,模式部分是非常相似甚至相同的,并且完全没有扩展性,后来添加算法只能重写。
设计暂停模块让交互更加友好,windows可以通过puase实现,我选择使用cin.get实现(linux),之后便是调用即可。
实现
基类
可以看到,增加求命中率的方法前是没有RunOnce函数的,只有Once函数,但是因为对于所有算法计算运行次数和缺页次数都是经Once接口的,所以只有再基类中添加两个计数器即可实现对求命中率的扩展,不必挨个算法添加。
在交互方面,通过基类的'<<'运算符来实现标准输出,通过Subjoin()虚函数来实现不同算法之间差异性结构的输出,拉至后面可以直接查看输出是什么样的。
class User
{
User(User&) = delete;
User& operator=(User&) = delete;
public:
User(int memoryCapacity)
: memory(new int[memoryCapacity])
, memoryCapacity(memoryCapacity)
, haveRep(false)
, lastRep(-1)
, lastRepNum(0)
, count(0)
, repCount(0)
{
memset(memory, -1, sizeof(int) * memoryCapacity);
}
virtual ~User()
{
delete[] memory;
}
virtual bool Once(int incId) = 0; // 缺页返回true
void RunOnce(int incId)
{
count++;
if (Once(incId))
repCount++;
}
float HitRate() const // 求命中率
{
return 1 - static_cast<float>(repCount) / count;
}
friend std::ostream& operator<<(std::ostream& os, const User& obj);
protected:
bool haveRep; // 上一步是否发生替换
int lastRep; // 上一步被替换的内容
int lastRepNum; // 上一步被替换的位置
int memoryCapacity; // 物理块大小(内存容量)
int* memory;
int count; // 计输入的指令次数
int repCount; // 计缺页的次数
virtual std::string Subjoin() const
{
return "";
}
};
std::ostream& operator<<(std::ostream& os, const User& obj)
{
os << " [ " << std::setw(3) << obj.memory[0];
for (int i = 1; i < obj.memoryCapacity; i++)
os << " | " << std::setw(3) << obj.memory[i];
os << " ]";
if (obj.haveRep)
os << " | 缺页-第" << (obj.lastRepNum + 1) << "个: [" << obj.lastRep
<< " -> " << obj.memory[obj.lastRepNum] << "] |";
os << obj.Subjoin();
return os;
}
主函数
先放出主函数部分,后面算法可以结合运行结果分析,查看。
分为三个部分,首先是随机生成指令序列并输出,没什么说的。然后是通过与用户交互进行多态指针的绑定,最后就是运行并输出结果,献给大家放几张运行图。
int main()
{ // 生成随机指令序列
IncSeq incSeq(RUN_NUM);
std::cout << "序列随机生成 " << RUN_NUM << " 个指令:\n"
<< incSeq << std::endl;
// 输入物理块数和选择页面置换算法
User* usr;
void init(User * &usr, IncSeq & incSeq);
init(usr, incSeq);
void run(User * &usr, IncSeq & incSeq);
run(usr, incSeq);
std::cout << "\npress any key to exit..." << std::endl;
std::cin.get();
return 0;
}
void init(User*& usr, IncSeq& incSeq)
{
char nop, y;
int x = 0;
std::cout << "请输入物理块数( >0 and not char ): ";
std::cin >> x;
if (x <= 0)
exit(1);
std::cout << std::endl;
std::cout << "1.FIFO 2.LRU 3.LFU 4.NRU 5.OPT" << std::endl;
while (true)
{
bool loop = false;
std::cout << "请选择页面置换算法: ";
std::cin >> y;
switch (y)
{
case '1':
usr = new FIFO(x);
break;
case '2':
usr = new LRU(x);
break;
case '3':
usr = new LFU(x);
break;
case '4':
usr = new NRU(x);
break;
case '5':
usr = new OPT(x, incSeq);
break;
default:
loop = true;
break;
}
if (!loop)
break;
}
std::cout << std::endl;
}
void run(User*& usr, IncSeq& incSeq)
{
int t = 0;
std::cin.get();
std::cout << "接下来按下任意键继续下一步...(按下'e'将跳至结束)"
<< std::endl;
std::cout << "then press any key to continue...(press 'e' to end last)"
<< std::endl;
std::cin.get();
system("clear");
std::cout << "\nT" << t << ":\n" << *usr << std::endl;
char nop = std::cin.get();
while (t < RUN_NUM)
{
usr->RunOnce(incSeq[t]);
std::cout << "\nT" << std::left << std::setw(3) << t + 1
<< ": | Inc: " << std::right << std::setw(3) << incSeq[t]
<< " | 命中率: " << usr->HitRate() << '\n'
<< *usr << std::endl;
t++;
if (nop != 'e')
nop = std::cin.get();
}
if (nop == 'e')
std::cin.get();
}
运行时图:
运行出来大概就是这种感觉,可以根据个人习惯调整,因为篇幅比较长所以分上下文,大家可以点击链接跳转至后篇。
下篇链接
【操作系统 实验9:页面置换算法模拟设计 (FIFO)(LRU)(OPT)(LFR)(NUR) --使用c++实现 (下) - CSDN App】