操作系统页面置换算法在C++中的实现

369 阅读6分钟

操作系统存储管理在C++中的实现

存储管理代码简介

存储管理的主要功能之一是合理地分配空间。请求页式管理是一种常用的虚拟存储管理技术。 本代码的目的是通过请求页式管理中页面置换算法模拟设计,了解虚拟存储技术的特点,掌握请求页式存储管理的页面置换算法。

在这里插入图片描述 在这里插入图片描述

代码实现

头文件及宏定义

#include<iostream>
#include<time.h>
#include<string>
#include<map>
using namespace std;
#define random(x) rand()%(x)

由于要使用字符串定义来增强用户使用的可观方便性,输入字符串(比如:FIFO、LRU)会比输入数字(比如:1、2、3)更容易理解和操作,所以引入头文件string。

我们还需使用map来构建数据结构,因为map的一一对应的优越性,不但可以保存指令号还可以保存在指令流中的序号,大大增强代码的内聚性,所以这里我们引入map头文件。 另外,我们需要用到随机数给出每个指令构建指令流,所以添加宏定义,方便后续代码随机数的使用,也提高代码的可读性。

同时需要插入头文件time.h作为随机数种子,让随机数真正随机。

指令流模块

我们分析程序的要求后发现,对于随机生成的页面流并不是完全随机那么简单的,我们需要让①50%的指令是顺序执行的②25%的指令是均匀分布在前地址部分③25%的指令是均匀分布在后地址部分。

这里我解释一下定义的指令,指令其实就是划分更细的页面,可以理解为0到9指令都算页面0,10到19都算页面1,如此下去直到页面31,总共32页。

正是因为指令划分的比较细,防止完全随机的数会引起无论什么算法页面一直在置换,失去模拟的意义,所以才引入了对随机数的要求。具体代码如下:

srand(time(NULL));
	multimap<int, int>insstream;
	int count = 0;
	while (count < 320) {
		int m = random(319);
		count++;
		insstream.insert(pair<int, int>(count,m+1));
		int m1 = random(m + 1);
		count++;
		insstream.insert(pair<int, int>(count, m1));
		count++;
		insstream.insert(pair<int, int>(count, m1+1));
		int m2 = random(319 - (m1 + 2) + 1) + m1 + 2;
		count++;
		insstream.insert(pair<int, int>(count, m2));
	}
	for (auto ins = insstream.begin(); ins != insstream.end(); ins++)
		cout << ins->second<<" ";

这里我们首先在代码的最开始定义随机数种子,让随机数完全随机。

再定义一个map数据结构来存储页面流,名为insstream(指令流)。

然后定义一个计数器,用于对指令计数,不仅可以在map里逐个标出指令顺序,还可以用于循环限制条件,控制循环结束。

然后按照前面的要求生成随机数,首先生成0-319的随机数,由于下面要让这个数+1作为指令号进入指令流,所以这里我定义为0-318取随机数,防止指令上溢;那么下溢问题呢,验算一下发现即使取0也不影响后续指令的选取,不会下溢。

这边注意我们要在每个指令插入指令流map之前就将count++,否则对应在map里的first值将不正确对应指令执行序号。

对于想要生成n ~ m的数,我们就用这个公式random(m-n+1)+n。所以这里要生成[m’+2,319]的随机指令就是random(319 - (m1 + 2) + 1) + m1 + 2。 在循环外我们使用迭代器来输出整个指令流检查是否正常运行,运行如下: 在这里插入图片描述

页面流模块

按照我们之前说的,指令流只是对于现实情况更加细致的模拟,而真正的页面置换依然是建立在页面的层面的,所以我们需要将刚刚的指令流转化为页面流。具体代码如下:

multimap<int, int>pagestream;
	for (auto ins = insstream.begin(); ins != insstream.end(); ins++)
		pagestream.insert(pair<int, int>(ins->first, ins->second/10));
	for (auto page = pagestream.begin(); page != pagestream.end(); page++)
		cout << page->second << " ";

同样地,我们建立一个名为pagestream(页面流)的map数据结构,用迭代器迭代指令流里每个指令转化为对应的页面:我们观察发现页面号刚好为指令号/10并且去掉小数部分,所以我们将pagestream的map依然定义为int int,这样可以直接将每个指令/10就得到对应的页面号。

在循环外我们也使用迭代器来输出整个页面流检查是否正常运行,运行如下: 在这里插入图片描述

选择模块

string choose;
	cout << "*************************************************" << endl;
	cout << "欢迎使用本程序~" << endl;
	int allocation = random(6 - 3 + 1) + 3;
	cout << "本次分配的物理块数为:" << allocation << "块!" << endl;
	while (true) {
		cout << "请选择您需要的算法(FIFO、LRU、OPT、LFR):" << endl;
		cout << "或输入S或s停止程序~" << endl;
		cin >> choose;
		double miss = 0;
		map<int, int>run;
		if (choose == "FIFO" || choose == "fifo") {
		
		}
		else if (choose == "LRU" || choose == "lru") {

		}
		else if (choose == "OPT" || choose == "opt") {

		}
		else if (choose == "LFR" || choose == "lfr") {

		}
		else if (choose == "S" || "s") { break; }
		else {
			cout << "您的输入有误!请输入:FIFO、LRU、OPT、LFR中的一个!" << endl;
		}
	}
	cout << "感谢您的使用~祝您生活愉快!" << endl;
	cout << "*************************************************" << endl;
}

对于选择模块需要注意的就是界面的可观性强,以及要考虑到用户输入错误以及大小写问题,我们需要提升代码的鲁棒性。

另外,在这一模块,我们将物理块数随机为3~6块,方便后续算法的编写。

同时,再定义出名为run的map数据结构,用来存放当前在物理块中的页面。

先进先出算法(FIFO)模块

if (choose == "FIFO" || choose == "fifo") {
			for (auto page = pagestream.begin(); page != pagestream.end(); page++)
			{
				for (auto r = run.begin(); r != run.end(); r++)
				{
					if (r->second == page->second) { goto skip; }
				}
				run.insert(pair<int, int>(page->first, page->second));
				miss++;
				if (run.size() > allocation)
					run.erase(run.begin());
			skip:
				cout << "";
				/*检测运行流
				for (auto r = run.begin(); r != run.end(); r++)
					cout << r->second << " ";
				cout << endl;
				*/
			}
			cout << "页面命中率=" << 1 - (miss / 320) << endl;
		}

首先我们先在最外层构建一个pagestream页面流的迭代器,用于逐个判断页面流中的页面。

在里面嵌套一层run物理块的迭代器,用于逐个判断当前页面是否和物理块中有相同的页面,如果有就直接不插入,并且跳出run迭代器的循环。

注意:一个容器的迭代器中不能给该容器插入或删除元素,否则会破坏迭代器结构,引起xtree报错或者运行结果始终不对!!!

所以这就是为什么我们这里使用goto的原因,因为插入不能在迭代器中完成,所以我们写在run迭代器的外层,并且用goto在页面命中时跳过插入语句。 这里的skip: cout << "";是为了做一个标记,并没有实际运转,如果没有这个cout就会报错。

检测运行流部分可以将注释符号删除来观察运行过程中物理块中的页面变化过程。

最后输出一个页面命中率即可,计算方式在本文开头有。

最近最久未访问算法(LRU)模块

else if (choose == "LRU" || choose == "lru") {
			for (auto page = pagestream.begin(); page != pagestream.end(); page++)
			{
				for (auto r = run.begin(); r != run.end(); r++)
				{
					if (r->second == page->second) {
						run.erase(r);
						goto skip2;
					}
				}
				miss++;
			skip2:
				run.insert(pair<int, int>(page->first, page->second));
				if (run.size() > allocation)
					run.erase(run.begin());
				/*检测运行流
				for (auto r = run.begin(); r != run.end(); r++)
					cout << r->second << " ";
				cout << endl;
				*/
			}
			cout << "页面命中率=" << 1 - (miss / 320) << endl;
		}

对于LRU算法,我们的思路就是,无论来的页面有没有和物理块中的重复,都要插入,因为这样可以将该页面的序号变大,从而排在run运行流的最尾端,实现LRU算法的思想。

在页面有重复时,删除掉物理块中该页面的数据,再插入页面号相同但在运行流中序号不同的数据;若没有重复,还是正常插入。

这里可以在迭代器中删除元素的原因是删除后直接goto跳出了循环,不再迭代,也就不考虑破坏迭代器结构了~

最佳淘汰算法(OPT)模块

else if (choose == "OPT" || choose == "opt") {
			for (auto page = pagestream.begin(); page != pagestream.end(); page++)
			{
				for (auto r = run.begin(); r != run.end(); r++)
				{
					if (r->second == page->second) { goto skip3; }
				}
				if (run.size() == allocation) {
					multimap<int, int>length;
					for (auto r = run.begin(); r != run.end(); r++) {
						for (auto page2 = page; page2 != pagestream.end(); page2++) {
							if (r->second == page2->second) {
								int len = page2->first - page->first;
								length.insert(pair<int, int>(len, r->first));
								break;
							}
							if (page2 == pagestream.find(pagestream.size())) {
								int len = 321 - page->first;
								length.insert(pair<int, int>(len, r->first));
								break;
							}
						}
					}
					auto last = length.rbegin(); 
					if (last != length.rend()) {
						auto r = run.find(last->second);
						if (r != run.end()) {
							run.erase(r);
						}
					}
				}
				run.insert(pair<int, int>(page->first, page->second));
				miss++;
			skip3:
				cout << "";
				/*检测运行流
				for (auto r = run.begin(); r != run.end(); r++)
					cout << r->second << " ";
				cout << endl;
				*/
			}
			cout << "页面命中率=" << 1 - (miss / 320) << endl;
		}

OPT算法是比较复杂的,他的思路并不像FIFO和LRU那么简单,除了判断新来的页面是否已经存在于物理块中,它还需要我们在需要发生置换时判断每个在物理块中的页面下一次出现与现在的“距离”。

判断是否相等依然用和前面一样的goto语句,可以快速跳出多重迭代器,简化代码。

这里当需要置换时我们并不能先插入后删除,因为需要给物理块中每个页面判断距离,如果先插入很可能会又被删除,很明显不符合页面置换算法。

所以判断条件变为了run.size() == allocation而不是上面的>。

在条件成立时,我们建立一个新的multimap(==长度可能会相同,为了防止数据丢失,必须使用multimap==)名为length用于存放物理块中每个物理块从当前位置到下一次出现的距离。

if (r->second == page2->second) {
	int len = page2->first - page->first;
	length.insert(pair<int, int>(len, r->first));
	break;
}
if (page2 == pagestream.find(pagestream.size())) {
	int len = 321 - page->first;
	length.insert(pair<int, int>(len, r->first));
	break;
}

这个两个语句是计算每个物理块中的页面从当前位置到下次出现的距离,如果不出现,距离就是总页面数(320)+1再减去当前位置。

把距离作为key值存放入length中,这样可以利用map的自动排序,将最久不会出现的页面放在最末端,将r->first(物理块中页面序号,同时也是run的key值)作为value值,这样我们需要删除run中元素时就可以轻松调用key值去删除。

auto last = length.rbegin(); 
					if (last != length.rend()) {
						auto r = run.find(last->second);
						if (r != run.end()) {
							run.erase(r);
						}
					}

这个模块特别重要!是一个对map深层的理解,如果不清楚这个可能会一直报错出错,还找不到任何逻辑错误!!!

当我们需要调用length最后一个元素的value值时,我们首先要找到它的迭代器,由于迭代器中的end()并不是指向末尾元素的,而是末尾元素的下一位,所以我们需要用我这里的定义才能正确返回length末尾元素的迭代器。有的教程会说,直接定义auto last = length.end();last--;就可以了,实际上这样很容易报错,提示调用了无效的参数,建议按照我的定义来,亲测不报错。

找到length末尾元素的迭代器后,再利用它的value值作为寻找run中key值的中间量,从而删除掉最久不会出现的元素。

成功删除后便可以插入了,然后缺页数miss++自增,最后在整个迭代器外计算命中率。

最近最少访问算法(LFU)模块

else if (choose == "LFU" || choose == "lfu") {
			for (auto page = pagestream.begin(); page != pagestream.end(); page++)
			{
				for (auto r = run.begin(); r != run.end(); r++)
				{
					if (r->second == page->second) { goto skip4; }
				}
				if (run.size() == allocation) {
					int time = 0;
					multimap<int, int>times;
					for (auto r = run.begin(); r != run.end(); r++) {
						for (auto page2 = pagestream.begin(); page2 != page; page2++) {
							if (r->second == page2->second) {
								time++;
							}
						}
						times.insert(pair<int, int>(time, r->first));
						time = 0;
					}
					run.erase(times.begin()->second);
				}
				run.insert(pair<int, int>(page->first, page->second));
				miss++;
			skip4:
				cout << "";
				/*检测运行流
				for (auto r = run.begin(); r != run.end(); r++)
					cout << r->second << " ";
				cout << endl;
				*/
			}
			cout << "页面命中率=" << 1 - (miss / 320) << endl;

LFU算法的实现和OPT非常相似,基本上稍微改一些就能实现了。

讲一下唯一的不同点:OPT算法是对于物理块中的页面检测后面再出现的距离,而LFU是检测前面出现的次数,OPT遍历当前位置的后面,LFU遍历前面。

我们只需要类似OPT地定义一个名为times的multimap来存放物理块中页面出现的次数即可。其余部分和OPT基本一致。

源代码及运行过程图片

下面是完善界面后的源代码,以及一次运行的过程截图:

#include<iostream>
#include<time.h>
#include<string>
#include<map>
using namespace std;
#define random(x) rand()%(x)
int main()
{
	srand(time(NULL));
	map<int, int>insstream;
	int count = 0;
	while (count < 320) {
		int m = random(319);
		count++;
		insstream.insert(pair<int, int>(count, m + 1));
		int m1 = random(m + 1);
		count++;
		insstream.insert(pair<int, int>(count, m1));
		count++;
		insstream.insert(pair<int, int>(count, m1 + 1));
		int m2 = random(319 - (m1 + 2) + 1) + m1 + 2;
		count++;
		insstream.insert(pair<int, int>(count, m2));
	}
	/*检测指令流
	for (auto ins = insstream.begin(); ins != insstream.end(); ins++)
		cout << ins->second << " ";
		*/
	map<int, int>pagestream;
	for (auto ins = insstream.begin(); ins != insstream.end(); ins++)
		pagestream.insert(pair<int, int>(ins->first, ins->second / 10));
	//检测页面流
	cout << "页面流如下:" << endl;
	for (auto page = pagestream.begin(); page != pagestream.end(); page++)
	{
		cout << page->second << " ";
		if (page->first % 32 == 0) { cout << endl; }
	}
	string choose;
	cout << "*************************************************" << endl;
	cout << "欢迎使用本程序~" << endl;
	int allocation = random(6 - 3 + 1) + 3;
	cout << "本次分配的物理块数为:" << allocation << "块!" << endl;
	while (true) {
		cout << "请选择您需要的算法(FIFO、LRU、OPT、LFU):" << endl;
		cout << "或输入S或s停止程序~" << endl;
		cin >> choose;
		double miss = 0;
		map<int, int>run;
		if (choose == "FIFO" || choose == "fifo") {
			for (auto page = pagestream.begin(); page != pagestream.end(); page++)
			{
				for (auto r = run.begin(); r != run.end(); r++)
				{
					if (r->second == page->second) { goto skip; }
				}
				run.insert(pair<int, int>(page->first, page->second));
				miss++;
				if (run.size() > allocation)
					run.erase(run.begin());
			skip:
				cout << "*****************" << endl;
				//检测运行流
				for (auto r = run.begin(); r != run.end(); r++)
					cout << r->second << " ";
				cout << endl;
			}
			cout << "*********************" << endl;
			cout << "FIFO算法的页面命中率=" << 1 - (miss / 320) << endl;
			cout << "*********************" << endl;
		}
		else if (choose == "LRU" || choose == "lru") {
			for (auto page = pagestream.begin(); page != pagestream.end(); page++)
			{
				for (auto r = run.begin(); r != run.end(); r++)
				{
					if (r->second == page->second) {
						run.erase(r);
						goto skip2;
					}
				}
				miss++;
			skip2:
				run.insert(pair<int, int>(page->first, page->second));
				if (run.size() > allocation)
					run.erase(run.begin());
				//检测运行流
				cout << "*****************" << endl;
				for (auto r = run.begin(); r != run.end(); r++)
					cout << r->second << " ";
				cout << endl;
			}
			cout << "*********************" << endl;
			cout << "LRU算法的页面命中率=" << 1 - (miss / 320) << endl;
			cout << "*********************" << endl;
		}
		else if (choose == "OPT" || choose == "opt") {
			for (auto page = pagestream.begin(); page != pagestream.end(); page++)
			{
				for (auto r = run.begin(); r != run.end(); r++)
				{
					if (r->second == page->second) { goto skip3; }
				}
				if (run.size() == allocation) {
					multimap<int, int>length;
					for (auto r = run.begin(); r != run.end(); r++) {
						for (auto page2 = page; page2 != pagestream.end(); page2++) {
							if (r->second == page2->second) {
								int len = page2->first - page->first;
								length.insert(pair<int, int>(len, r->first));
								break;
							}
							if (page2 == pagestream.find(pagestream.size())) {
								int len = 321 - page->first;
								length.insert(pair<int, int>(len, r->first));
								break;
							}
						}
					}
					auto last = length.rbegin(); 
					if (last != length.rend()) {
						auto r = run.find(last->second);
						if (r != run.end()) {
							run.erase(r);
						}
					}
				}
				run.insert(pair<int, int>(page->first, page->second));
				miss++;
			skip3:
				cout << "*****************" << endl;
				//检测运行流
				for (auto r = run.begin(); r != run.end(); r++)
					cout << r->second << " ";
				cout << endl;
				
			}
			cout << "*********************" << endl;
			cout << "OPT算法的页面命中率=" << 1 - (miss / 320) << endl;
			cout << "*********************" << endl;
		}
		else if (choose == "LFU" || choose == "lfu") {
			for (auto page = pagestream.begin(); page != pagestream.end(); page++)
			{
				for (auto r = run.begin(); r != run.end(); r++)
				{
					if (r->second == page->second) { goto skip4; }
				}
				if (run.size() == allocation) {
					int time = 0;
					multimap<int, int>times;
					for (auto r = run.begin(); r != run.end(); r++) {
						for (auto page2 = pagestream.begin(); page2 != page; page2++) {
							if (r->second == page2->second) {
								time++;
							}
						}
						times.insert(pair<int, int>(time, r->first));
						time = 0;
					}
					run.erase(times.begin()->second);
				}
				run.insert(pair<int, int>(page->first, page->second));
				miss++;
			skip4:
				cout << "*****************" << endl;
				//检测运行流
				for (auto r = run.begin(); r != run.end(); r++)
					cout << r->second << " ";
				cout << endl;
			}
			cout << "*********************" << endl;
			cout << "LFU算法的页面命中率=" << 1 - (miss / 320) << endl;
			cout << "*********************" << endl;
		}
		else if (choose == "S" || "s") { break; }
		else {
			cout << "您的输入有误!请输入:FIFO、LRU、OPT、LFU中的一个!" << endl;
		}
	}
	cout << "感谢您的使用~祝您生活愉快!" << endl;
	cout << "*************************************************" << endl;
}

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

总结

对于页面置换算法在C++中实现会比进程调度以及银行家算法都要困难很多,数据结构的定义以及算法的选择都很重要。map这种一一对应的容器可以很好地存放页面的数据,对于map调用的底层规则在本文也有细节讲述,希望能给大家一些帮助。本文作于2021.5.24。