赞
踩
系列文章:
算法 | 注释 |
---|---|
最优算法 | 不可实现,但可用作基准 |
NRU(最近未使用)算法 | LRU的很粗糙的近似 |
FIFO(先进先出)算法 | 可能抛弃重要页面 |
第二次机会算法 | 比FIFO有很大的改善 |
时钟算法 | 现实的 |
LRU(最近最少使用)算法 | 很优秀,但很难实现 |
NFU(最不经常使用)算法 | LRU的相对粗略的近似 |
老化算法 | 非常近似LRU的有效算法 |
工作集算法 | 实现起来开销很大 |
工作集时钟算法 | 好的有效算法 |
在系统运行工程中,当发生缺页中断时,操作系统必须在内存中选择一个页面将其换出内存。那么怎么置换就十分重要了,我们想让它产生的缺页中断最少,算法的设计十分关键。
最好的算法,往往是不能实现。为什么不能,肯定是机器太笨,没有别的解释了。
最优页面置换算法,是当发生缺页中断时,我们就去寻找今后不用的页面或者用的最晚的那一个页面,将它替换出去。如果一个页面在100万条指令内不会被使用,另外一个页面在60万指令内不会被使用,则置换前一个页面,从而将缺页中断推迟,越久越好。
还是来个图简单解释一下吧,希望我能讲清楚。
当程序需要页面E的时候,发现页面B在后面不用了,那么就把页面B置换掉。需要页面F也是一样。
问题来了,怎么知道人家不用呢?跑一遍,拿个小本本记下来,想P呢!也许有用但没有必要!
既然不能预知未来,那么我们就回首过往。
在大部分的虚拟内存的计算机中,系统为每一个页面设置了两个状态位。当页面访问(读或写)时设置R位;当修改页面时设置W位,这些都需要包含在页表中。一个固定的时钟周期,去清零R位,区别最近没有被访问的页面和被访问的页面。
知识补充说明
:
页表:就是页面的属性集合。
页表项结构:
页框号:最重要,页的映射就是要找到这个值
“在/不在”:标志页面在不在内存中。
保护位:一个页允许什么类型访问。(读/写)
修改位:标志该页面有没有被修改
访问位:标志该页面有没有被访问
修改位和访问位一般由硬件进行设置
高速缓存禁止位:禁止该页面被高速缓存
NRU(Not Recently Used)算法,在最近一个时钟周期内,淘汰一个没有被访问的已修改页面要比淘汰一个被频繁使用的“干净”页面好。
当产生页面中断时,操作系统会根据读写位把他们分成4类:
第0类:没有被访问,没有被修改
第1类:没有被访问,已被修改
第2类:已被访问,没有修改
第3类:已被访问,已被修改
第1类看起来不可能,其实在第3类,经过一个时钟周期,访问位被清零,就产生了该现象。
下面我们利用软件代码,模拟一下,该算法。
终于能够看到代码了!!!
页表项我们仅仅模拟修改位和访问位。
/* * 页表项 * 仅用软件模拟 访问位R 和 修改位W */ template<class T> class PageInfo { public: PageInfo(T* dat); ~PageInfo(); const int getW() const; //获取访问位R void setR_1(); //设置访问位R为1 void setR_0(); //设置访问位R为0 const int getR() const; //获取修改位W void setW_1(); //设置修改位W为1 void setW_0(); //设置修改位W为0 T* data; private: int W, R; };
模拟内存和设置需要页面
const int MAX = 4; //模拟内存能容忍最大页面
//模拟使用页面
PageInfo page_A(new string("A"));
PageInfo page_B(new string("B"));
PageInfo page_C(new string("C"));
PageInfo page_D(new string("D"));
PageInfo page_E(new string("E"));
PageInfo page_F(new string("F"));
void* Memery[MAX] = {nullptr}; //模拟内存
模拟时钟清零访问位
//模拟系统清零访问位
template<class T>
void ClearR()
{
std::this_thread::sleep_for(std::chrono::microseconds(20)); //设置时间间隔 20ms
while (1)
{
for (int i = 0; i < MAX; i++)
{
((PageInfo<T>*)Memery[i])->setR_0();
}
}
}
NRU算法实现
/* * NUR 主要判断需不需要替换 * 根据NUR原则替换所需页面 * 传入所需页面&是否需要修改 */ template<class T> void NRU(PageInfo<T>& page,int W) { if (1 == W) //需要修改 { page.setW_1(); } page.setR_1(); int i = 0; for (; i < MAX; i++) //判断页面是否存在 { if (Memery[i] == (void*)&page) { break; } if (nullptr == ((PageInfo<T>*)Memery[i])) { cout << "缺页中断" << endl; Memery[i] = (void*)&page; break; } } //如果不存在 if (i == MAX && ((PageInfo<T>*)Memery[MAX-1] != &page)) { cout << "缺页中断" << endl; int index = 0; //最佳替换的索引 PageInfo<T>* t = (PageInfo<T>*)Memery[0]; for (int i = 0; i < MAX; i++) { //该页面在一个时钟周期内,未被访问 if (0 == ((PageInfo<T>*)Memery[i])->getR() && 0 == ((PageInfo<T>*)Memery[i])->getW()) { index = i; break; } //判断谁被访问过,访问过优先 if (0 == ((PageInfo<T>*)Memery[i])->getW() && t->getR() == 1) { t = (PageInfo<T>*)Memery[i]; index = i; continue; } //都被访问过,选择出未被修改过 if (1 == ((PageInfo<T>*)Memery[i])->getR() && t->getR() == 1) { if (((PageInfo<T>*)Memery[i])->getW() < t->getW()) { t = ((PageInfo<T>*)Memery[i]); index = i; } continue; } } //替换之前 设置访问位R为0 ((PageInfo<T>*)Memery[index])->setR_0(); if (((PageInfo<T>*)Memery[index])->getW() == 1) { //写回数据,设置修改位W为0 cout << *(((PageInfo<T>*)Memery[index])->data) << "页面被修改,写回......" << endl; ((PageInfo<T>*)Memery[index])->setW_0(); } Memery[index] = (void*)&page; } }
测试函数:
/* * data.txt * 0 1 * 1 0 * 2 0 * 3 0 * 1 1 * 4 0 * 5 1 * 1 1 * 2 1 * 0 0 * 4 1 */ //0,1,2,3,4,5 对应页面 page_A,page_B........ //W 为修改位,1 为修改 0 不修改 int main() { if (freopen("data.txt", "r", stdin) == NULL) { exit(0); } while (1) { int page = 0,W = 0; cin >> page >> W; switch (page) { case 0: NRU(page_A, W); break; case 1: NRU(page_B, W); break; case 2: NRU(page_C, W); break; case 3: NRU(page_D, W); break; case 4: NRU(page_E, W); break; case 5: NRU(page_F, W); break; default: break; } } std::thread thread_clear(ClearR<string>); //启动线程,按照时钟周期清零 thread_clear.join(); return 0; }
NRU主要优点是易于理解和能够有效地被实现,虽然它的性能不是很好,但是已经够用了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。