目录
一、实验目的
-
理解虚拟内存和物理内存之间的关系及转化,在什么情况下会发生缺页中断。
-
理解和掌握各种页面置换算法的工作原理,包括先进先出(FIFO)、最近最少使用(LRU)、时钟置换(CLOCK)和随机置换算法,并通过模拟实验观察它们的执行过程。
-
通过比较不同页面置换算法下系统的性能,理解不同算法的优缺点以及在何种情况下更适用。
-
学习如何评估系统性能。通过实验结果,理解如何通过缺页中断次数、平均访问时间等指标去评估系统性能,并能合理地解释实验结果。
-
从实验中体验和理解内存访问的局部性原理,提高自己对操作系统虚拟内存管理的理解。
二、实验要求
-
设计并实现一个模拟的简单虚拟页式存储管理系统。在该系统中,应能模拟以下功能:创建进程的页表,为进程页面分配内存块,实现逻辑地址向物理地址的转换。当发生缺页时,通过缺页中断处理调入页面,如果需要,可以进行页面置换。
-
制定一套模拟数据,包括要访问的页面、需执行的操作等,并按照虚拟页式存储管理的原则进行处理,展示每一步的执行结果。
-
能够模拟出页面置换的情况,例如FIFO、LRU、CLOCK和随机置换等算法,比较并分析每种算法下系统的性能。
三、实验代码
#include<iostream>
#include<cstdio>
#include<vector>
#include<string>
#include<queue>
#include<cmath>
#include<functional>
#include<stdlib.h>
#include<time.h>
using namespace std;
const int N = 15;//PCB上限个数
const int PAGE_SIZE = pow(2,12);//4096,页面大小设置为4KB
const int M = pow(2,2);//4,内存页面个数
//const int M = 3;
const int INF = 0x3f3f3f3f;//极大值
//进程操作类型
enum PcbOptionType{
VISIT,//访问
MODIFY,//修改
};
//定义进程操作的数据结构,将操作拆分为代表逻辑地址的指令和操作类型
struct PcbOption{
string instruction;//指令(逻辑地址),长度为22位
PcbOptionType option;//要执行的操作
};
//定义页表项的数据结构
struct PageTableItem{
int frameNum = -1;//页帧(也称页框、内存块)号,初始状态为-1
int status = 0;//状态位,表示该页面是否调入内存,status为1代表已调入,初始状态为0
int exmemAddress = 0;//外存地址,保存页面在外存中的存放位置
//访问字段,方便页面置换算法进行操作
int selectedTime = INF;//页面被放入内存的时间
int visitedTime = INF;//页面最近一次被访问的时间
int visit = 0;//访问位,表示页面近期是否被访问,0代表近期未被访问
int dirty = 0;//修改位,表示页面调入内存后是否被修改过,dirty为1代表已修改,初始状态为0
//初始化函数
PageTableItem()
{
frameNum = -1;
status = 0;
dirty = 0;
exmemAddress = 0;
}
};
//定义PCB的数据结构
struct PCB{
int id;//进程编号
vector<PcbOption> options;//进程操作序列
vector<PageTableItem> pageTable;//储存在进程中的页表
}p[N];
//定义内存的数据结构,表示一个内存块
struct Memory{
int value = 0;//内存块中是否存放数据,0代表内存块为空,1代表内存块存在数据,初始状态为0
int pageNum = -1;//页号,代表当前内存块被进程的哪一个页面占用,-1代表内存块未被占用
vector<int> memData;//模拟每个内存块中每个字节存放的数据,0代表没有数据,1代表有数据
}mem[M];
//FIFO算法优先队列比较函数
struct cmp
{
bool operator()(const int &a, const int &b)//selectTime小的靠近队首
{
return p[0].pageTable[a].selectedTime > p[0].pageTable[b].selectedTime;
}
};
int pcb_num = 1;//该系统默认只运行一个进程
function<int()> algorithmOption;//选择使用的替换算法
int missingCount = 0;//统计缺页中断次数
int replaceCount = 0;//统计置换次数
int t = 0;//保存页面加入内存的时间
int clockPoint = 0;//CLOCK算法用到的指针,保存指向的页帧号,初始为0
//地址转换函数,输入逻辑地址,输出物理地址
int LogicalToPhysical(string instruction, PcbOptionType option)
{
//将二进制的指令转化为十进制
int tmp = 0;
for(int i = 0; i < instruction.size(); i++)
{
tmp <<= 1;
if(instruction[i] == '1')
tmp += 1;
}
//获得页号和页内偏移量
int pageNum = tmp / PAGE_SIZE;
int offset = tmp % PAGE_SIZE;
//cout << "tmp:" << tmp << " " << pageNum << " " << offset << '\n';
//判断页号是否越界
if(pageNum >= p[0].pageTable.size())//页号大于等于页表项个数,则发生越界(内中断),输出报错信息
{
printf("指令%s发生越界,页号:%d,页表项个数:%d\n", instruction, pageNum, p[0].pageTable.size());
return -1;
}
//查询页表
if(p[0].pageTable[pageNum].status == 1)//如果该页面已调入内存,无需置换
{
printf("%d号页面已调入内存,访问命中\n", pageNum);
p[0].pageTable[pageNum].visitedTime = t;
p[0].pageTable[pageNum].visit = 1;
return p[0].pageTable[pageNum].frameNum * PAGE_SIZE + offset;//返回物理地址
}
else//如果该页面未调入内存
{
bool flag = 0;//表示是否采用置换算法,0表示未采用
missingCount++;//增加缺页次数
//寻找内存中空的内存块
int mem_id = -1;//内存中空的内存块的编号,-1代表没有空的内存块
for(int i = 0; i < M; i++)
{
if(mem[i].value == 0)
{
mem_id = i;
break;
}
}
//cout << "mem_id_1:" << mem_id << '\n';
if(mem_id == -1)//如果内存中没有空的内存块,采用置换算法获得内存块
{
flag = 1;
mem_id = algorithmOption();
replaceCount++;//增加置换次数
}
//cout << "mem_id_2:" << mem_id << '\n';
if(!flag)
printf("%d号页面未调入内存,发生缺页,将%d号页面放入空内存块%d\n", pageNum, pageNum, mem_id);
else
printf("%d号页面未调入内存,发生缺页,采用替换算法将%d号页面放入内存块%d,替换%d号页面\n",
pageNum, pageNum, mem_id, mem[mem_id].pageNum);
mem[mem_id].pageNum = pageNum;//更新页框所对应的页号
mem[mem_id].value = 1;//页框被占用
p[0].pageTable[pageNum].frameNum = mem_id;//将页表项中的页帧号更新为mem_id
p[0].pageTable[pageNum].status = 1;//将页表项中的状态位更新为1
//更新页表项中的访问字段
p[0].pageTable[pageNum].selectedTime = t;
p[0].pageTable[pageNum].visitedTime = t;
p[0].pageTable[pageNum].visit = 1;//
if(option == MODIFY)
p[0].pageTable[pageNum].dirty = 1;
return mem_id * PAGE_SIZE + offset;//返回物理地址
}
}
//FIFO算法
int FIFO()
{
priority_queue<int, vector<int>, cmp> q;//;以页面调入内存的先后顺序排成的队列,保存页号
if(q.empty())//如果队列为空,则按照页面访问顺序查找在内存中的页面
for(int i = 0; i < p[0].pageTable.size(); i++)
if(p[0].pageTable[i].status == 1)
q.push(i);
int pageNum = q.top();//从队头取出要替换的页帧(最早进入内存的页面)所对应的页号
q.pop();
int frameNum = p[0].pageTable[pageNum].frameNum;//获得对应页帧号
p[0].pageTable[pageNum].status = 0;//原来的页面移除内存
q.push(pageNum);//再将页号放入队尾
return frameNum;//返回要替换的页帧号
}
//LRU算法
int LRU()
{
int minn = INF;
int frameNum = -1;//要替换的页帧号
int pageNum = -1;//要替换的页帧所对应的页号
for(int i = 0; i < M; i++)
{
if(p[0].pageTable[mem[i].pageNum].visitedTime < minn)//如果该页面是最近最久未使用的页面,则替换
{
minn = p[0].pageTable[mem[i].pageNum].visitedTime;
pageNum = mem[i].pageNum;//保存要替换的页帧所对应的页号
frameNum = i;//保存要替换的页帧号
}
}
p[0].pageTable[pageNum].status = 0;//原来的页面移除内存
return frameNum;//返回要替换的页帧号
}
//CLOCK算法
int CLOCK()
{
int frameNum = -1;//要替换的页帧号
int pageNum = -1;//要替换的页帧所对应的页号
printf("\n当前循环队列:\n");
printf("%6s %8s %6s %6s\n", "页帧号", "对应页号", "访问位", "修改位");
for(int k = 0; k < M; k++)//打印链式结构
{
if(k != 0)
{
printf(" |\n");
printf(" | |\n");
printf(" v |\n");
printf(" |\n");
}
if(k == 0)
{
printf("%-6d %-8d %-6d %-6d<----\n", k, mem[k].pageNum,
p[0].pageTable[mem[k].pageNum].visit, p[0].pageTable[mem[k].pageNum].dirty);
}
else if(k == M - 1)
{
printf("%-6d %-8d %-6d %-6d-----\n", k, mem[k].pageNum,
p[0].pageTable[mem[k].pageNum].visit, p[0].pageTable[mem[k].pageNum].dirty);
}
else
{
printf("%-6d %-8d %-6d %-6d |\n", k, mem[k].pageNum,
p[0].pageTable[mem[k].pageNum].visit, p[0].pageTable[mem[k].pageNum].dirty);
}
}
printf("当前替换指针指向的页帧号:%d\n\n", clockPoint);
for(int i = 1; i <= 4; i++)//从第一轮到第四轮
{
if(i == 1)//第一轮,查找第一个访问位和修改位均为0的页面,不修改任何标志位
{
for(int k = 0; k < M; k++)
{
int j = (clockPoint + k) % M;//扫描到的页帧号
//如果该页面的访问位和修改位均为0,则替换
if(p[0].pageTable[mem[j].pageNum].visit == 0 && p[0].pageTable[mem[j].pageNum].dirty == 0)
{
pageNum = mem[j].pageNum;//保存要替换的页帧所对应的页号
frameNum = j;//保存要替换的页帧号
break;
}
}
}
else if(i == 2)//第二轮,查找第一个访问位为0,修改位为1的页面,将所有扫描过的帧访问位置为0
{
for(int k = 0; k < M; k++)
{
int j = (clockPoint + k) % M;//扫描到的页帧号
//如果该页面的访问位为0,修改位为1,则替换
if(p[0].pageTable[mem[j].pageNum].visit == 0 && p[0].pageTable[mem[j].pageNum].dirty == 1)
{
pageNum = mem[j].pageNum;//保存要替换的页帧所对应的页号
frameNum = j;//保存要替换的页帧号
break;
}
p[0].pageTable[mem[j].pageNum].visit = 0;//将所有扫描过的帧访问位置为0
}
}
else if(i == 3)//第三轮,查找第一个访问位和修改位均为0的页面,不修改任何标志位
{
for(int k = 0; k < M; k++)
{
int j = (clockPoint + k) % M;//扫描到的页帧号
//如果该页面的访问位和修改位均为0,则替换
if(p[0].pageTable[mem[j].pageNum].visit == 0 && p[0].pageTable[mem[j].pageNum].dirty == 0)
{
pageNum = mem[j].pageNum;//保存要替换的页帧所对应的页号
frameNum = j;//保存要替换的页帧号
break;
}
}
}
else//第四轮,查找第一个访问位为0,修改位为1的页面,必然可以找到
{
for(int k = 0; k < M; k++)
{
int j = (clockPoint + k) % M;//扫描到的页帧号
//如果该页面的访问位为0,修改位为1,则替换
if(p[0].pageTable[mem[j].pageNum].visit == 0 && p[0].pageTable[mem[j].pageNum].dirty == 1)
{
pageNum = mem[j].pageNum;//保存要替换的页帧所对应的页号
frameNum = j;//保存要替换的页帧号
break;
}
}
}
if(frameNum != -1)//如果找到了要替换的页帧,直接返回
{
p[0].pageTable[pageNum].status = 0;//原来的页面移除内存
clockPoint = (frameNum + 1) % M;//指针指向下一个页帧
return frameNum;//返回要替换的页帧号
}
}
}
//随机置换算法(RRA)
int RRA()
{
int frameNum = -1;//要替换的页帧号
int pageNum = -1;//要替换的页帧所对应的页号
srand(time(NULL));
frameNum = rand() % M;//页帧号取值区间为0到M-1
pageNum = mem[frameNum].pageNum;//保存要替换的页帧所对应的页号
p[0].pageTable[pageNum].status = 0;//原来的页面移除内存
return frameNum;//返回要替换的页帧号
}
//输入模块
void Input()
{
//初始化内存,每个内存块有PAGE_SIZE个字节
for(int i = 0; i < M; i++)
mem[i].memData.resize(PAGE_SIZE, 0);
for(int i = 0; i < pcb_num; i++)
{
//初始化进程页表
int pageTableLength;//页表长度
cout << "请输入页表长度:" << '\n';
cin >> pageTableLength;
for(int j = 0; j < pageTableLength; j++)
{
PageTableItem pti;//每个页表项初始为默认值
p[i].pageTable.push_back(pti);//将页表项放入页表
}
//初始化进程操作序列
int optionNum;//操作数量
cout << "请输入进程操作数量optionNum:" << '\n';
cin >> optionNum;
cout << "接下来optionNum行,请分别输入指令和操作类型:" << '\n';
for(int j = 0; j < optionNum; j++)
{
string instruction;//指令
int opType;//操作类型,0代表访问,1代表修改
cin >> instruction >> opType;//输入指令和操作类型
PcbOption po;
po.instruction = instruction;
if(opType == 0)
po.option = VISIT;
else
po.option = MODIFY;
p[i].options.push_back(po);//将该操作放入操作序列
}
}
}
//算法选择模块
void ChooseAlgorithm()
{
cout << "请选择将要使用的页面置换算法:" << '\n';
cout << "1.先进先出算法(FIFO)" << '\n';
cout << "2.最近最久未使用算法(LRU)" << '\n';
cout << "3.时钟置换算法(CLOCK)" << '\n';
cout << "4.随机置换算法(RRA)" << '\n';
int op;
cin >> op;
switch(op)
{
case 1:
algorithmOption = FIFO;
break;
case 2:
algorithmOption = LRU;
break;
case 3:
algorithmOption = CLOCK;
break;
case 4:
algorithmOption = RRA;
break;
default:
break;
}
}
//运行测试模块
void RunTest()
{
cout << "运行过程:" << '\n';
//逐条执行进程操作序列
for(int i = 0; i < p[0].options.size(); i++)
{
int physicalAddress = LogicalToPhysical(p[0].options[i].instruction, p[0].options[i].option);//逻辑地址转化为物理地址
cout << "逻辑地址: " << p[0].options[i].instruction << " -> 物理地址: " << physicalAddress << '\n';
t++;//时间自增
}
}
//统计数据模块
void ShowData()
{
cout << "内存数据:" << '\n';
printf("%6s %8s\n", "页帧号", "进程页号");
for(int i = 0; i < M; i++)
printf("%-6d %-8d\n", i, mem[i].pageNum);
cout << "页表数据:" << '\n';
printf("%4s %6s %6s %6s %6s\n", "页号", "页帧号", "状态位", "访问位", "修改位");
for(int i = 0; i < p[0].pageTable.size(); i++)
printf("%-4d %-6d %-6d %-6d %-6d\n", i,
p[0].pageTable[i].frameNum, p[0].pageTable[i].status,
p[0].pageTable[i].visit, p[0].pageTable[i].dirty);
cout << "访问次数:" << p[0].options.size() << '\n';
cout << "缺页中断次数:" << missingCount <<'\n';
cout << "置换次数:" << replaceCount << '\n';
cout << "缺页中断率:" << (double)missingCount / p[0].options.size() * 100 << "%\n";
}
int main()
{
Input();//输入进程要执行的操作序列
ChooseAlgorithm();//选择将要执行的页面置换算法
RunTest();//运行测试
ShowData();//显示统计数据
return 0;
}
四、实验代码解读
4.1前言
模拟实验代码语言为c++。
4.2数据结构
4.2.1PCB的数据结构
//定义PCB的数据结构
struct PCB{
int id;//进程编号
vector<PcbOption> options;//进程操作序列
vector<PageTableItem> pageTable;//储存在进程中的页表
}p[N];
PCB主要保存进程编号、进程操作序列和进程中的页表三个属性。该系统中只有一个p[0]进程运行,该进程模拟所有的页面访问。
4.2.2进程操作的数据结构
//定义进程操作的数据结构,将操作拆分为代表逻辑地址的指令和操作类型
struct PcbOption{
string instruction;//指令(逻辑地址),长度为22位
PcbOptionType option;//要执行的操作
};
PcbOption是进程操作的数据结构,将操作拆分为代表逻辑地址的指令和操作类型。逻辑地址表示p[0]进程将要访问的页面。
4.2.3页表项的数据结构
//定义页表项的数据结构
struct PageTableItem{
int frameNum = -1;//页帧(也称页框、内存块)号,初始状态为-1
int status = 0;//状态位,表示该页面是否调入内存,status为1代表已调入,初始状态为0
int exmemAddress = 0;//外存地址,保存页面在外存中的存放位置
//访问字段,方便页面置换算法进行操作
int selectedTime = INF;//页面被放入内存的时间
int visitedTime = INF;//页面最近一次被访问的时间
int visit = 0;//访问位,表示页面近期是否被访问,0代表近期未被访问
int dirty = 0;//修改位,表示页面调入内存后是否被修改过,dirty为1代表已修改,初始状态为0
//初始化函数
PageTableItem()
{
frameNum = -1;
status = 0;
dirty = 0;
exmemAddress = 0;
}
};
PageTableItem是页表项的数据结构。PageTableItem保存页帧号frameNum、状态位status和外存地址exmemAddress等基本属性,同时保持selectedTime、visitedTime、visit和dirty等访问字段,方便页面置换算法进行操作。
4.2.4内存的数据结构
//定义内存的数据结构,表示一个内存块
struct Memory{
int value = 0;//内存块中是否存放数据,0代表内存块为空,1代表内存块存在数据,初始状态为0
int pageNum = -1;//页号,代表当前内存块被进程的哪一个页面占用,-1代表内存块未被占用
vector<int> memData;//模拟每个内存块中每个字节存放的数据,0代表没有数据,1代表有数据
}mem[M];
Memory是内存块的数据结构,内存块大小M=3。一个内存块中保存数据value、页号pageNum等属性。
4.3主要函数
4.3.1地址转换函数
//地址转换函数,输入逻辑地址,输出物理地址
int LogicalToPhysical(string instruction, PcbOptionType option)
{
//将二进制的指令转化为十进制
int tmp = 0;
for(int i = 0; i < instruction.size(); i++)
{
tmp <<= 1;
if(instruction[i] == '1')
tmp += 1;
}
//获得页号和页内偏移量
int pageNum = tmp / PAGE_SIZE;
int offset = tmp % PAGE_SIZE;
//cout << "tmp:" << tmp << " " << pageNum << " " << offset << '\n';
//判断页号是否越界
if(pageNum >= p[0].pageTable.size())//页号大于等于页表项个数,则发生越界(内中断),输出报错信息
{
printf("指令%s发生越界,页号:%d,页表项个数:%d\n", instruction, pageNum, p[0].pageTable.size());
return -1;
}
//查询页表
if(p[0].pageTable[pageNum].status == 1)//如果该页面已调入内存,无需置换
{
printf("%d号页面已调入内存,访问命中\n", pageNum);
p[0].pageTable[pageNum].visitedTime = t;
p[0].pageTable[pageNum].visit = 1;
return p[0].pageTable[pageNum].frameNum * PAGE_SIZE + offset;//返回物理地址
}
else//如果该页面未调入内存
{
bool flag = 0;//表示是否采用置换算法,0表示未采用
missingCount++;//增加缺页次数
//寻找内存中空的内存块
int mem_id = -1;//内存中空的内存块的编号,-1代表没有空的内存块
for(int i = 0; i < M; i++)
{
if(mem[i].value == 0)
{
mem_id = i;
break;
}
}
//cout << "mem_id_1:" << mem_id << '\n';
if(mem_id == -1)//如果内存中没有空的内存块,采用置换算法获得内存块
{
flag = 1;
mem_id = algorithmOption();
replaceCount++;//增加置换次数
}
//cout << "mem_id_2:" << mem_id << '\n';
if(!flag)
printf("%d号页面未调入内存,发生缺页,将%d号页面放入空内存块%d\n", pageNum, pageNum, mem_id);
else
printf("%d号页面未调入内存,发生缺页,采用替换算法将%d号页面放入内存块%d,替换%d号页面\n",
pageNum, pageNum, mem_id, mem[mem_id].pageNum);
mem[mem_id].pageNum = pageNum;//更新页框所对应的页号
mem[mem_id].value = 1;//页框被占用
p[0].pageTable[pageNum].frameNum = mem_id;//将页表项中的页帧号更新为mem_id
p[0].pageTable[pageNum].status = 1;//将页表项中的状态位更新为1
//更新页表项中的访问字段
p[0].pageTable[pageNum].selectedTime = t;
p[0].pageTable[pageNum].visitedTime = t;
p[0].pageTable[pageNum].visit = 1;//
if(option == MODIFY)
p[0].pageTable[pageNum].dirty = 1;
return mem_id * PAGE_SIZE + offset;//返回物理地址
}
}
地址转换函数将逻辑地址转换为物理地址。该函数先根据逻辑地址计算出页号和页内偏移量,再判断页号是否越界。如果页号未越界就查询页表,未命中的情况下会在内存中寻找空的内存块或者采用页面置换算法。
4.3.2FIFO算法
//FIFO算法优先队列比较函数
struct cmp
{
bool operator()(const int &a, const int &b)//selectTime小的靠近队首
{
return p[0].pageTable[a].selectedTime > p[0].pageTable[b].selectedTime;
}
};
//FIFO算法
int FIFO()
{
priority_queue<int, vector<int>, cmp> q;//;以页面调入内存的先后顺序排成的队列,保存页号
if(q.empty())//如果队列为空,则按照页面访问顺序查找在内存中的页面
for(int i = 0; i < p[0].pageTable.size(); i++)
if(p[0].pageTable[i].status == 1)
q.push(i);
int pageNum = q.top();//从队头取出要替换的页帧(最早进入内存的页面)所对应的页号
q.pop();
int frameNum = p[0].pageTable[pageNum].frameNum;//获得对应页框号
p[0].pageTable[pageNum].status = 0;//原来的页面移除内存
q.push(pageNum);//再将页号放入队尾
return frameNum;//返回要替换的页帧号
}
设置一个优先队列,保存调入内存的页面的页号。每次从队头取出最早进入内存的页面的页号。
4.3.3LRU算法
//LRU算法
int LRU()
{
int minn = INF;
int frameNum = -1;//要替换的页帧号
int pageNum = -1;//要替换的页帧所对应的页号
for(int i = 0; i < M; i++)
{
if(p[0].pageTable[mem[i].pageNum].visitedTime < minn)//如果该页面是最近最久未使用的页面,则替换
{
minn = p[0].pageTable[mem[i].pageNum].visitedTime;
pageNum = mem[i].pageNum;//保存要替换的页帧所对应的页号
frameNum = i;//保存要替换的页帧号
}
}
p[0].pageTable[pageNum].status = 0;//原来的页面移除内存
return frameNum;//返回要替换的页帧号
}
LRU算法遍历内存中的页面,通过页面最近一次被访问的时间visitedTime来找出最近最久未使用的页面,替换该页面。
4.3.4CLOCK算法
//CLOCK算法
int CLOCK()
{
int frameNum = -1;//要替换的页帧号
int pageNum = -1;//要替换的页帧所对应的页号
printf("\n当前循环队列:\n");
printf("%6s %8s %6s %6s\n", "页帧号", "对应页号", "访问位", "修改位");
for(int k = 0; k < M; k++)//打印链式结构
{
if(k != 0)
{
printf(" |\n");
printf(" | |\n");
printf(" v |\n");
printf(" |\n");
}
if(k == 0)
{
printf("%-6d %-8d %-6d %-6d<----\n", k, mem[k].pageNum,
p[0].pageTable[mem[k].pageNum].visit, p[0].pageTable[mem[k].pageNum].dirty);
}
else if(k == M - 1)
{
printf("%-6d %-8d %-6d %-6d-----\n", k, mem[k].pageNum,
p[0].pageTable[mem[k].pageNum].visit, p[0].pageTable[mem[k].pageNum].dirty);
}
else
{
printf("%-6d %-8d %-6d %-6d |\n", k, mem[k].pageNum,
p[0].pageTable[mem[k].pageNum].visit, p[0].pageTable[mem[k].pageNum].dirty);
}
}
printf("当前替换指针指向的页帧号:%d\n\n", clockPoint);
for(int i = 1; i <= 4; i++)//从第一轮到第四轮
{
if(i == 1)//第一轮,查找第一个访问位和修改位均为0的页面,不修改任何标志位
{
for(int k = 0; k < M; k++)
{
int j = (clockPoint + k) % M;//扫描到的页帧号
//如果该页面的访问位和修改位均为0,则替换
if(p[0].pageTable[mem[j].pageNum].visit == 0 && p[0].pageTable[mem[j].pageNum].dirty == 0)
{
pageNum = mem[j].pageNum;//保存要替换的页帧所对应的页号
frameNum = j;//保存要替换的页帧号
break;
}
}
}
else if(i == 2)//第二轮,查找第一个访问位为0,修改位为1的页面,将所有扫描过的帧访问位置为0
{
for(int k = 0; k < M; k++)
{
int j = (clockPoint + k) % M;//扫描到的页帧号
//如果该页面的访问位为0,修改位为1,则替换
if(p[0].pageTable[mem[j].pageNum].visit == 0 && p[0].pageTable[mem[j].pageNum].dirty == 1)
{
pageNum = mem[j].pageNum;//保存要替换的页帧所对应的页号
frameNum = j;//保存要替换的页帧号
break;
}
p[0].pageTable[mem[j].pageNum].visit = 0;//将所有扫描过的帧访问位置为0
}
}
else if(i == 3)//第三轮,查找第一个访问位和修改位均为0的页面,不修改任何标志位
{
for(int k = 0; k < M; k++)
{
int j = (clockPoint + k) % M;//扫描到的页帧号
//如果该页面的访问位和修改位均为0,则替换
if(p[0].pageTable[mem[j].pageNum].visit == 0 && p[0].pageTable[mem[j].pageNum].dirty == 0)
{
pageNum = mem[j].pageNum;//保存要替换的页帧所对应的页号
frameNum = j;//保存要替换的页帧号
break;
}
}
}
else//第四轮,查找第一个访问位为0,修改位为1的页面,必然可以找到
{
for(int k = 0; k < M; k++)
{
int j = (clockPoint + k) % M;//扫描到的页帧号
//如果该页面的访问位为0,修改位为1,则替换
if(p[0].pageTable[mem[j].pageNum].visit == 0 && p[0].pageTable[mem[j].pageNum].dirty == 1)
{
pageNum = mem[j].pageNum;//保存要替换的页帧所对应的页号
frameNum = j;//保存要替换的页帧号
break;
}
}
}
if(frameNum != -1)//如果找到了要替换的页帧,直接返回
{
p[0].pageTable[pageNum].status = 0;//原来的页面移除内存
clockPoint = (frameNum + 1) % M;//指针指向下一个页帧
return frameNum;//返回要替换的页帧号
}
}
}
所有可能被置换的页面形成一个循环队列,clockPoint表示替换指针,visit表示访问位,dirty表示修改位。CLOCK最多经过四轮遍历,找出要替换的页面。
4.3.5RRA随机置换算法
//随机置换算法(RRA)
int RRA()
{
int frameNum = -1;//要替换的页帧号
int pageNum = -1;//要替换的页帧所对应的页号
srand(time(NULL));
frameNum = rand() % M;//页帧号取值区间为0到M-1
pageNum = mem[frameNum].pageNum;//保存要替换的页帧所对应的页号
p[0].pageTable[pageNum].status = 0;//原来的页面移除内存
return frameNum;//返回要替换的页帧号
}
利用srand函数模拟RRA算法,随机选择一个页面置换。
4.4用户交互
4.4.1输入模块
//输入模块
void Input()
{
//初始化内存,每个内存块有PAGE_SIZE个字节
for(int i = 0; i < M; i++)
mem[i].memData.resize(PAGE_SIZE, 0);
for(int i = 0; i < pcb_num; i++)
{
//初始化进程页表
int pageTableLength;//页表长度
cout << "请输入页表长度:" << '\n';
cin >> pageTableLength;
for(int j = 0; j < pageTableLength; j++)
{
PageTableItem pti;//每个页表项初始为默认值
p[i].pageTable.push_back(pti);//将页表项放入页表
}
//初始化进程操作序列
int optionNum;//操作数量
cout << "请输入进程操作数量optionNum:" << '\n';
cin >> optionNum;
cout << "接下来optionNum行,请分别输入指令和操作类型:" << '\n';
for(int j = 0; j < optionNum; j++)
{
string instruction;//指令
int opType;//操作类型,0代表访问,1代表修改
cin >> instruction >> opType;//输入指令和操作类型
PcbOption po;
po.instruction = instruction;
if(opType == 0)
po.option = VISIT;
else
po.option = MODIFY;
p[i].options.push_back(po);//将该操作放入操作序列
}
}
}
输入模块要求用户输入页表长度、操作数量和指令序列。
4.4.2算法选择模块
//算法选择模块
void ChooseAlgorithm()
{
cout << "请选择将要使用的页面置换算法:" << '\n';
cout << "1.先进先出算法(FIFO)" << '\n';
cout << "2.最近最久未使用算法(LRU)" << '\n';
cout << "3.时钟置换算法(CLOCK)" << '\n';
cout << "4.随机置换算法(RRA)" << '\n';
int op;
cin >> op;
switch(op)
{
case 1:
algorithmOption = FIFO;
break;
case 2:
algorithmOption = LRU;
break;
case 3:
algorithmOption = CLOCK;
break;
case 4:
algorithmOption = RRA;
break;
default:
break;
}
}
算法选择模块要求用户选择要执行的页面置换算法。
4.4.3统计数据模块
//统计数据模块
void ShowData()
{
cout << "内存数据:" << '\n';
printf("%6s %8s\n", "页帧号", "进程页号");
for(int i = 0; i < M; i++)
printf("%-6d %-8d\n", i, mem[i].pageNum);
cout << "页表数据:" << '\n';
printf("%4s %6s %6s %6s %6s\n", "页号", "页帧号", "状态位", "访问位", "修改位");
for(int i = 0; i < p[0].pageTable.size(); i++)
printf("%-4d %-6d %-6d %-6d %-6d\n", i,
p[0].pageTable[i].frameNum, p[0].pageTable[i].status,
p[0].pageTable[i].visit, p[0].pageTable[i].dirty);
cout << "访问次数:" << p[0].options.size() << '\n';
cout << "缺页中断次数:" << missingCount <<'\n';
cout << "置换次数:" << replaceCount << '\n';
cout << "缺页中断率:" << (double)missingCount / p[0].options.size() * 100 << "%\n";
}
统计数据模块显示内存数据、页表数据和一些性能指标。
4.4.4主函数
int main()
{
Input();//输入进程要执行的操作序列
ChooseAlgorithm();//选择将要执行的页面置换算法
RunTest();//运行测试
ShowData();//显示统计数据
return 0;
}
主函数依次执行输入模块、算法选择模块、运行测试和统计数据模块。
五、实验结果
5.1FIFO算法测试结果
如下图所示:

FIFO测试数据(页面访问序列3 2 1 0 3 2 4 3 2 1 0 4):
5
12
011000000000000 0
010000000000000 0
001000000000000 0
000000000000000 0
011000000000000 0
010000000000000 0
100000000000000 0
011000000000000 0
010000000000000 0
001000000000000 0
000000000000000 0
100000000000000 0
5.2LRU算法测试结果
如下图所示:

LRU测试数据:(页面访问序列1 8 1 7 8 2 7 2 1 8 3 8 2 1 3 1 7 1 3 7):
9
20
0001000000000000 0
1000000000000000 0
0001000000000000 0
0111000000000000 0
1000000000000000 0
0010000000000000 0
0111000000000000 0
0010000000000000 0
0001000000000000 0
1000000000000000 0
0011000000000000 0
1000000000000000 0
0010000000000000 0
0001000000000000 0
0011000000000000 0
0001000000000000 0
0111000000000000 0
0001000000000000 0
0011000000000000 0
0111000000000000 0
5.3CLOCK算法测试结果
如下图所示:


CLOCK测试数据(两组,对比着用。页面访问序列1 3 4 2 5 6 3 4 7):
8
9
001000000000000 0
011000000000000 0
100000000000000 0
010000000000000 0
101000000000000 0
110000000000000 0
011000000000000 0
100000000000000 0
111000000000000 0
8
9
001000000000000 0
011000000000000 0
100000000000000 1
010000000000000 0
101000000000000 0
110000000000000 0
011000000000000 0
100000000000000 0
111000000000000 0
5.4随机置换算法测试结果
这个没什么好说的了,毕竟是随机的对吧。
六、结语
本实验设计思路参考王道考研操作系统,代码均为本人原创。如果有什么错误,欢迎大伙儿在评论区指正ヾ(●´▽‘●)ノ。
1796

被折叠的 条评论
为什么被折叠?



