您的位置:首页 > 运维架构

模拟实现FIFO,LRU,OPT内存淘汰策略

2015-05-04 20:46 821 查看

模拟实现FIFO,LRU,OPT内存淘汰策略

策略简介

在页式存储管理中,内存以页框为单位分配使用。程序运行时以页为单位装入内存,只把当前需要的若干页装入内存,且这些页占用的页框不必相邻。程序运行需要新的页时,按需从外存上调入内存。但当物理内存中的页不够用的时候,要装入新的页就必须淘汰物理内存中某些页框的内容.

FIFO:first input first output的缩写,很容易理解这种策略的思想.

这种算法的实质是:总是选择在主存中停留时间最长(即最老)的一页置换,即先进入内存的页,先退出内存.

理由是:最早调入内存的页,其不再被使用的可能性比刚调入内存的可能性大.

建立一个FIFO队列,收容所有在内存中的页。被置换页面总是在队列头上进行。当一个页面被放入内存时,就把它插在队尾上.

优点:实现简单:页面按进入内存的时间排序,淘汰队头页面.

LRU:Least Recently Used,最久未使用.

LRU算法是与每个页面最后使用的时间有关的。当必须置换一个页面时,LRU算法选择过去一段时间里最久未被使用的页面。

LRU算法是经常采用的页面置换算法,,并被认为是相当好的,但是存在如何实现它的问题.

1.硬件方法:

页面设置一个移位寄存器R。每当页面被访问则将其重置1.

周期性地(周期很短)将所有页面的R左移一位(右边补0)

当需要淘汰页面时选择R值最大的页.

R的位数越多且移位周期越小就越精确,但硬件成本高.

2.计数器:

保存时间:最简单的情况是使每个页表项对应一个使用时间字段,并给CPU增加一个逻辑时钟或计数器。每次存储访问,该时钟都加1。每当访问一个页面时,时钟寄存器的内容就被复制到相应页表项的使用时间字段中。这样我们就可以始终保留着每个页面最后访问的“时间”。

在置换页面时,选择该时间值最小的页面。

这样做,不仅要查页表,而且当页表改变时(因CPU调度)要维护这个页表中的时间,还要考虑到时钟值溢出的问题。

3.栈:

用一个栈保留页号。

每当访问一个页面时,就把它从栈中取出放在栈顶上。这样一来,栈顶总是放有目前使用最多的页,而栈底放着目前最少使用的页。

由于要从栈的中间移走一项,所以要用具有头尾指针的双向链连起来。

在最坏的情况下,移走一页并把它放在栈顶上需要改动6个指针。每次修改都要有开销,但需要置换哪个页面却可直接得到,用不着查找,因为尾指针指向栈底,其中有被置换页。

OPT:optimal

该策略的思想是,淘汰不再需要或最远的将来才会使用到的页面。

特点:理论上最佳,但实践中无法实现。因为当缺页发生时,操作系统无法知道各个页面下一次是在什么时候被访问

虽然这个算法不可能实现,但是最佳页面置换算法可以用于对可实现算法的性能进行衡量比较。

模拟实现(C语言)

这里我们仅仅用简单的数组实现。假设一虚拟页中存放10条指令,虚拟页数为32,物理页数4-32

定义指令集合结构体

typedef struct commandList{
int command;//10条指令的集合,代表所在的虚拟页页号
int offset;//指令偏移地址
int lastTimeHit;//最后一次命中的时间
int enterNum;//模拟进入的顺序
};


定义物理内存页,这里简单的写入加载虚拟页的页号

typedef struct physicalPage{
int command;//装入的指令集,-1代表没有装入
};


虚拟页中存放些什么数据呢?这里我们用随机数模拟虚拟页中的指令.

其中COMMAND_NUM代表指令个数,这里将虚拟页装满取320

void initVirtualPage(int * vp){
srand((unsigned)time(NULL));
for (int i = 0; i < COMMAND_NUM; i++)
{
vp[i] = rand()% COMMAND_NUM;
//  printf("%d  ", vp[i]);
}
}


定义全局变量

//物理页数
int physicalPageNum;
//虚拟页,int存储的是虚拟页中的指令,该指令随机产生
int* mVirtualPages;
//物理页
physicalPage* mPhysicalPage;
//指令集
commandList* mCommandList;
//缺页次数
int disaffect = 0;
//缺页率
float disaffectRate;


初始化变量

//初始化指令集合
void initCommandList(commandList* cl){
for (int i = 0; i < COMMAND_NUM; i++)
{
//方便起见,指令集和下标即为该指令
(cl + i)->lastTimeHit = -1;
(cl + i)->command = i / PAGE_CONTENT;
(cl + i)->offset = i % PAGE_CONTENT;
(cl + i)->enterNum = 0;
}
}
//初始化物理内存页
void initPhysicalPage(physicalPage* pp){
for (int i = 0; i < physicalPageNum; i++)
{
(pp+i)
4000
->command = -1;
}
}


实现核心判断:是否命中(是否存在物理页中)

这里用简单的循环判断,若存在则返回所在位置,否则返回-1

int isExistInPhysicalPage(int command, physicalPage*pp, int length){
for (int i = 0; i < length; i++)
{
if ((pp + i)->command == command) return i;
}
return FALSE;
}


FIFO策略淘汰的页号.

int whichIsToSelectFIFO(physicalPage*pp, int length){
int returnValue = 0;
int target = (mCommandList + pp->command*PAGE_CONTENT)->enterNum;
for (int i = 0; i < length; i++)
{
//若物理内存为空,则直接返回该位置
if ((pp + i)->command == -1) return i;
//若不为空,则返回最先进入内存的页面
else
{
int tmp = (mCommandList + (pp + i)->command*PAGE_CONTENT)->enterNum;
if (tmp < target){
target = tmp;
returnValue = i;
}
}
}
return returnValue;
}


这里将commandList中的PAGE_CONTENT倍数的指令记录最近访问时间

(因为mPAGE_CONTENT到(m+1)PAGE_CONTENT-1的指令要运行,都要将m虚拟页加载到内存中),即记录某虚拟页请求分配内存并进入内存的最初时间.

模拟FIFO访问内存

void FIFO(){
for (int i = 0; i < COMMAND_NUM; i++)
{
int command =* (mVirtualPages+i)/PAGE_CONTENT;
#ifdef DEBUG
printf_s("---------------------------------------------------------------------------\n");
printf_s("指令%d请求分配内存,", *(mVirtualPages + i));
#endif
//是否存在物理页?若存在,返回所在位置,否则返回-1
int index = isExistInPhysicalPage(command, mPhysicalPage, physicalPageNum);
// 若不存在
if (index == FALSE){
disaffect++;
/*模拟执行装入内存*/
int targetIndex = whichIsToSelectFIFO(mPhysicalPage, physicalPageNum);
//写入
(mPhysicalPage + targetIndex)->command = command;
#ifdef DEBUG
printf_s("物理内存%d上的指令被淘汰,取而代之的是%d\n", targetIndex, *(mVirtualPages + i));
#endif
//更新使用情况
(mCommandList + command*PAGE_CONTENT)->enterNum = i+1;
(mCommandList + command*PAGE_CONTENT)->lastTimeHit = i;
}
//否则直接更新
else
{
#ifdef DEBUG
printf_s("该指令已经存在内存中!\n");
#endif
(mCommandList + command*PAGE_CONTENT)->lastTimeHit = i;
}
#ifdef DEBUG
printf_s("内存使用情况:\n");
//输出内存使用情况
for (int k = 0; k < physicalPageNum; k++)
{
printf_s(" %d ", (mPhysicalPage + k)->command);
}
printf_s("\n");
#endif
}
}


for循环模拟不断从虚拟页取指令,取出指令赋给command(代表虚拟页号),判断该虚拟页是否存在物理页中,若存在,返回位置,并更新该虚拟页最近命中的时间,若不存在,则根据FIFO策略找出被淘汰的内存并更新

- LRU淘汰策略

int whichIsToSelectLRU(physicalPage*pp, int length){
int returnValue = 0;
int target = (mCommandList + pp->command*PAGE_CONTENT)->lastTimeHit;
for (int i = 0; i < length; i++)
{
//若物理内存为空,则直接返回该位置
if ((pp + i)->command == -1) return i;
//若不为空,则返回最先进入内存的页面
else
{
int tmp = (mCommandList + (pp + i)->command*PAGE_CONTENT)->lastTimeHit;
if (tmp < target){
target = tmp;
returnValue = i;
}
}
}
return returnValue;
}


和FIFO一样,用PAGE_CONTENT倍数的指令记录虚拟页访问内存的信息,用指标lastTimeHit记录最近一次被命中的时间。lastTimeHit最小,则表明中间不使用的时间越长。

模拟LRU访问内存,模拟过程同上

void LRU(){
for (int i = 0; i < COMMAND_NUM; i++)
{
int command = *(mVirtualPages + i)/PAGE_CONTENT;

#ifdef DEBUG
printf_s("---------------------------------------------------------------------------\n");
printf_s("指令%d请求分配内存,", *(mVirtualPages+i));
#endif

//是否存在物理页?若存在,返回所在位置,否则返回-1
int index = isExistInPhysicalPage(command, mPhysicalPage, physicalPageNum);
// 若不存在
if (index == FALSE){
disaffect++;
/*模拟执行装入内存*/
int targetIndex = whichIsToSelectLRU(mPhysicalPage, physicalPageNum);
//写入
(mPhysicalPage + targetIndex)->command = command;
#ifdef DEBUG
printf_s("物理内存%d上的指令被淘汰,取而代之的是%d\n", targetIndex, *(mVirtualPages + i));
#endif
//更新使用情况
//
(mCommandList + command*PAGE_CONTENT)->enterNum = i + 1;
(mCommandList + command*PAGE_CONTENT)->lastTimeHit = i;
}
//存在内存中直接更新
else
{
#ifdef DEBUG
printf_s("该指令已经存在内存中!\n");
#endif
(mCommandList + command*PAGE_CONTENT)->lastTimeHit = i;
}
#ifdef DEBUG
printf_s("内存使用情况:\n");
//输出内存使用情况
for (int k = 0; k < physicalPageNum; k++)
{
printf_s(" %d ", (mPhysicalPage + k)->command);
}
printf_s("\n");
#endif
}
}


OPT淘汰策略

int whichIsToSelectOPT(physicalPage*pp, int length,int startNum){
int returnValue = 0;
int target=0;
int tmp=-1;
for (int i = startNum; i < COMMAND_NUM; i++){
//若找到与当前内存相同的最近的指令
if (pp->command == *(mVirtualPages + i) / PAGE_CONTENT) {
target = i - startNum + 1;
break;
}
else
{
//否则是最远距离
target = COMMAND_NUM - startNum + 1;
}
}
//第一个已经考虑过了
for (int i = 1; i < length; i++)
{
//若物理内存为空,则直接返回该位置
if ((pp + i)->command == -1) return i;
//若不为空,则返回最先进入内存的页面
else
{
for (int k= startNum; k< COMMAND_NUM; k++){
//若找到与当前内存相同的最近的指令
if ((pp+i)->command == *(mVirtualPages + k) / PAGE_CONTENT) {
tmp = k- startNum + 1;
break;
}
else
{
//否则最远距离
tmp = COMMAND_NUM - startNum + 1;
}
}
if (tmp >target){
target = tmp;
returnValue = i;
}
}
}
return returnValue;
}


上面的代码片段中,从startNum开始考虑最远的,或者是不需要的页面

模拟OPT策略

void OPT(){
for (int i = 0; i < COMMAND_NUM; i++)
{
int command = *(mVirtualPages + i) / PAGE_CONTENT;

#ifdef DEBUG
printf_s("---------------------------------------------------------------------------\n");
printf_s("指令%d请求分配内存,", *(mVirtualPages + i));
#endif
//是否存在物理页?若存在,返回所在位置,否则返回-1
int index = isExistInPhysicalPage(command, mPhysicalPage, physicalPageNum);
// 若不存在
if (index == FALSE){
//缺页
disaffect++;
int targetIndex;
/*模拟执行装入内存*/
//若已经到达数列末尾
if (i + 1 == COMMAND_NUM) targetIndex = 0;
else    targetIndex= whichIsToSelectOPT(mPhysicalPage, physicalPageNum,i+1);
//写入
(mPhysicalPage + targetIndex)->command = command;
#ifdef DEBUG
printf_s("物理内存%d上的指令被淘汰,取而代之的是%d\n", targetIndex, *(mVirtualPages + i));
#endif
//更新使用情况
(mCommandList + command)->enterNum = i + 1;
(mCommandList + command)->lastTimeHit = i;
}
//否则直接更新
else
{
#ifdef DEBUG
printf_s("该指令已经存在内存中!\n");
#endif
(mCommandList + command)->lastTimeHit = i;
}
#ifdef DEBUG
printf_s("内存使用情况:\n");
//输出内存使用情况
for (int k = 0; k < physicalPageNum; k++)
{

a814
printf_s(" %d ", (mPhysicalPage + k)->command);
}
printf_s("\n");
#endif
}
}


上面的代码中,有一个小细节,当i到达指令末尾且未命中时,直接替代0号物理内存页

有了上面的函数后,就能计算各策略的命中率了,主函数给出如下

int _tmain(int argc, _TCHAR* argv[])
{
while (true)
{
while (true)
{
puts("请输入物理页数:");
scanf_s("%d", &physicalPageNum);
//初始化全局变量
if (physicalPageNum < 4 || physicalPageNum>32)
{
puts("输入错误,请输入大于4小于32的整数");
}
else
{
break;
}
}
mPhysicalPage = (physicalPage*)malloc(physicalPageNum*sizeof(physicalPage));
//这里模拟32个不同的指令集合
mCommandList = (commandList*)malloc(COMMAND_NUM*sizeof(commandList));
mVirtualPages = (int*)malloc(COMMAND_NUM*sizeof(int));
initCommandList(mCommandList);
initPhysicalPage(mPhysicalPage);
initVirtualPage(mVirtualPages);
disaffect = 0;
printf_s("FIFO策略:\n");
FIFO();
disaffectRate = (float)disaffect / (float)COMMAND_NUM;
printf_s("命中率为%f\n", 1 - disaffectRate);
//重置某些数据
initCommandList(mCommandList);
initPhysicalPage(mPhysicalPage);
disaffect = 0;
printf_s("\n\nLRU策略:\n");
LRU();
disaffectRate = (float)disaffect / (float)COMMAND_NUM;
printf_s("命中率为%f\n", 1 - disaffectRate);
//重置某些数据
initCommandList(mCommandList);
initPhysicalPage(mPhysicalPage);
disaffect = 0;
printf_s("\n\nOPT策略:\n");
OPT();
disaffectRate = (float)disaffect / (float)COMMAND_NUM;
printf_s("命中率为%f\n", 1 - disaffectRate);
}
system("pause");
return 0;
}


运行截图:



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息