操作系统实验报告2.docx
《操作系统实验报告2.docx》由会员分享,可在线阅读,更多相关《操作系统实验报告2.docx(14页珍藏版)》请在冰豆网上搜索。
操作系统实验报告2
Nachos虚存实验报告
一、实验名称:
Nachos虚存
二、实验目的:
本实验牵涉到Nachos虚存子系统的部分实现,也是第一次用到Nachos的虚拟机。
测试代码是一组覆盖了绝大部分虚存空间的数组操作,该虚存空间被映射到20个物理页中。
地址转换和页表结构已给出。
本实验的目的是要实现缺页处理程序,这需要在适当的时候将某些页面替换出/入。
为了减少缺页和将页面从内存淘汰到磁盘的次数,要求实现以下五种页面替换算法:
ØNRU(NotRecentlyUsed)算法
ØSC(SecondChance)算法
ØClock算法
ØWorkingSet算法
ØAging算法
三、实验步骤
1.预备
(1)素材:
本实验的源代码是/home/nachos/mp3.tar文件。
复制到本地目录后解包,要在userprog子目录下工作,也需阅读文件filesys/filesys.h,filesys/openfile.h,machine/translate.h,machine/timer.h,和bin/noff.h。
本实验需修改文是memmanager.h和memmanager.cpp。
(2)分析代码:
每个地址空间都有相应的页表,页表中的每一项称为页表条目。
在本实验中,我们会牵涉到其中的三个主要字段:
USE、DIRTY和VALID,它们在machine/translate.h中有详细描述。
AddrSpace结构包含了Nachos进程地址空间的所有信息:
页表、页表长度等,以及操作地址空间的函数。
当创建了一个新的进程时,该结构也就随之产生并初始化。
所提供的代码已实现了页面调度机制,在该机制中,仅在一个执行进程访问页面时,才将它从可执行的源文件中读入物理内存。
所以,在进程刚创建时,页表中的所有条目都是初始化为无效的(所有页表条目的有效位都置为false)。
然而,访问这样的页面(不属于任何程序段)一般来说是不合法的。
当初始化一个页表时,要生成一个报告,该报告说明哪些页面的访问是合法的,而哪些页面的访问是不合法的(Legal字段)。
每个可执行程序都以一个头开始,它指定程序中所有程序段的虚存范围。
开始时,调用AddrSpace:
:
ReadSourcePage()函数来从可执行文件中读一个页面到物理内存。
注意,该函数仅在进程第一次访问虚页时调用。
至于后续的访问,如果页面不在物理内存中,则搜索替换文件而不是源文件。
替换文件由类似与MainMemory的内存帧组成,只不过它们存储在磁盘上。
一组称为“SwapOwners”的TranslationEntry指针用来跟踪指向替换文件中页面的页表条目,一个类似的结构“CoreOwners”则用来跟踪指向内存中页面的页表条目。
在执行阶段,仿真程序将在TranslationEntrys中设置适当的位,如:
当出现写操作时置DIRTY=TRUE、当出现写或读操作时置USE=TRUE。
如果仿真器处理了这样一个TranslationEntry中的存储器请求:
LEGAL=TRUE但VALID=FALSE,它将自陷到MemManager:
:
faultIn()中去,这就是你要实现的地方:
给定缺页的TranslationEntry,用PageIn()、PageOut()以及你要实现的将适当的缓冲页面调入物理页的方法,更新TranslationEntry并将控制权返回给虚拟机。
阅读代码MemManager:
:
PageFaultExceptionHandler(),它由ExceptionHandler()在接收到一个缺页异常时调用。
关于如何实现MemManager:
:
faultIn()的一些细节和几点提示都以注释的方式在MemManager:
:
PageFaultExceptionHandler()中给出。
注意:
在此函数中,程序计数器的值不应加一。
在处理完异常且控制权返回给缺页进程后,引起缺页的那条指令将重新执行。
2.页面替换算法的实现
本次实验对页面替换算法的实现主要是通过一个MemManagerr类来实现的,类中主要的函数有以下几个:
●faultIn是“handler”,而其它各种方法则聚合成了重要的功能:
●PageOut负责将页面写到备份存储中,它只处理脏页,因为其它的页面已在备份存储中或是与原始状态相比没有变化,所以其它的页面可以覆盖它;
●PageIn负责将页面读入指定的物理页,若该页不在备份存储中,则从原始文件中装载;
●MakeFreeFrame负责在没有空闲页时用适当的页面替换算法来选择一个牺牲页;
●doUpdation是一个定时中断处理程序(在memmanager.cc文件中),它修改translationEntries中适当的位来更新历史信息。
整个实验修改的代码如下所示:
(红色部分为修改的代码)
intMemManager:
:
makeFreeFrame()
{
//victimisthenumberofthephysicalpagetobeswappedout
intvictim=0;
switch(policy)
{
casePAGEREPL_NRU:
//4.4.2-NotRecentlyUsed
{
#ifdefCHANGE
inti;
boolfind=false;
for(i=0;(i!
=NumPhysPages)&&(!
find);i++)
{
if((!
coreOwners[i]->use)&&(!
coreOwners[i]->dirty))
{
victim=i;
find=true;
}
}
for(i=0;(i!
=NumPhysPages)&&(!
find);i++)
{
if((!
coreOwners[i]->use)&&(coreOwners[i]->dirty))
{
victim=i;
find=true;
}
}
for(i=0;(i!
=NumPhysPages)&&(!
find);i++)
{
if((coreOwners[i]->use)&&(!
coreOwners[i]->dirty))
{
victim=i;
find=true;
}
}
#endif
break;
}
casePAGEREPL_FIFO:
//4.4.3-FIFO
{
int*ptr=fifoList->Remove();
victim=*ptr;
#ifdefCHANGE
if(ptr!
=NULL)
deleteptr;
#endif
break;
}
casePAGEREPL_SC:
//4.4.4-SecondChance
{
#ifdefCHANGE
int*ptr=fifoList->Remove();
while(coreOwners[*ptr]->use)
{
coreOwners[*ptr]->use=false;
fifoList->Append(ptr);
ptr=fifoList->Remove();
}
victim=*ptr;
if(ptr!
=NULL)
deleteptr;
#endif
break;
}
casePAGEREPL_CLOCK:
//4.4.5-Clock
{
#ifdefCHANGE
while(coreOwners[clock_hand]->use)
{
coreOwners[clock_hand]->use=false;
clock_hand=(clock_hand+1)%NumPhysPages;
}
victim=clock_hand;
clock_hand=(clock_hand+1)%NumPhysPages;
#endif
break;
}
casePAGEREPL_WS:
//4.4.8-WorkingSet
{
intv_timestamp=stats->totalTicks;
#ifdefCHANGE
for(inti=0;i!
=NumPhysPages;i++)
{
if(coreOwners[i]->timeStamp{
v_timestamp=coreOwners[i]->timeStamp;
victim=i;
}
}
#endif
break;
}
casePAGEREPL_AGING:
//4.4.7-Aging
{
unsignedintv_bitmask=0xFFFF;
#ifdefCHANGE
for(inti=0;i!
=NumPhysPages;i++)
{
if(history[i]{
v_bitmask=history[i];
victim=i;
}
}
#endif
break;
}
voidMemManager:
:
doUpdation(intarg)
{
inti;
//MP3-makeanyneededchangestothisfunction
if(someVerbose)
printf("(update)%c",(++formatCount%5==0?
'\n':
','));
switch(policy)
{
casePAGEREPL_NRU:
//NotRecentlyUsedSection4.4.2
{
#ifdefCHANGE
inti;
for(i=0;i!
=NumPhysPages;i++)
{
if(coreFreeMap->Test(i))
coreOwners[i]->use=false;
}
#endif
break;
}
casePAGEREPL_AGING:
//AgingSection4.4.7
{
#ifdefCHANGE
for(i=0;i!
=NumPhysPages;i++)
{
if(coreFreeMap->Test(i))
{
history[i]=history[i]>>1;
if(coreOwners[i]->use)
{
history[i]=history[i]|(1<<(hbits-1));
coreOwners[i]->use=FALSE;
}
}
}
#endif
break;
}
//thefollowingdon'tusethetimerinterrupt
casePAGEREPL_FIFO:
//notused
casePAGEREPL_SC:
//notused
casePAGEREPL_CLOCK:
//notused
casePAGEREPL_WS:
//notused
default:
break;
}//endswitch
return;
}
voidMemManager:
:
pageIn(TranslationEntry*PTEntry,intphysFrame)
{
………………………………
//MP3-youneedtomakechangestothehistorykeepingalgorithmshere
//FIFOandSecondChancehavebeenimplementedforyou
switch(policy)
{
casePAGEREPL_FIFO:
//4.4.3-FIFO
casePAGEREPL_SC:
//4.4.4-SC
{
int*ptr=newint;
*ptr=physFrame;
fifoList->Append(ptr);
break;
}
casePAGEREPL_AGING:
//4.4.7-AGING
{
#ifdefCHANGE
history[physFrame]=bitmask;
#endif
break;
}
casePAGEREPL_NRU:
//4.4.2-NRU
casePAGEREPL_CLOCK:
//4.4.5-CLOCK
casePAGEREPL_WS:
//4.4.8-WS
default:
break;
}
return;
}
(1)NRU算法的实现:
此算法是用R位和M位来构造的,R位在每次时间中断是被清零,以区别最近没有被访问和被访问的页面,在系统中,R位和M位分别对应TranslationEntrys中的use和dirty字段,每次中断时,系统会调用doUpdation来进行中断处理,因此我们必须在doUpdation中将内存中所有页面的R位清零,也就是将use字段设为false。
具体实现是在switch语句中的casePAGEREPL_NRU中加入:
inti;
for(i=0;i!
=NumPhysPages;i++)
{
if(coreFreeMap->Test(i))
coreOwners[i]->use=false;
}
要注意的是,在设置use字段时,如果此时coreOwners[i]指向的内存页面为空就会发生严重的错误,所以我们必须在设置之前加入一个判断语句if(coreFreeMap->Test(i)),通过测试空闲位图的第i位(0为空,1为非空),来判断此页是否为空。
当页面发生失效时,根据页面的R位和M位将其分为4类:
●第0类:
use=false,dirty=false
●第1类:
use=false,dirty=true
●第2类:
use=true,dirty=false
●第3类:
use=true,dirty=true
我们将从最小编号的非空类中,选择第一个页面将其淘汰,具体实现是在makeFreeFrame中switch语句的casePAGEREPL_NRU中加入3个for循环:
inti;
boolfind=false;
for(i=0;(i!
=NumPhysPages)&&(!
find);i++)
{
if((!
coreOwners[i]->use)&&(!
coreOwners[i]->dirty))
{
victim=i;
find=true;
}
}
for(i=0;(i!
=NumPhysPages)&&(!
find);i++)
{
if((!
coreOwners[i]->use)&&(coreOwners[i]->dirty))
{
victim=i;
find=true;
}
}
for(i=0;(i!
=NumPhysPages)&&(!
find);i++)
{
if((coreOwners[i]->use)&&(!
coreOwners[i]->dirty))
{
victim=i;
find=true;
}
}
这三个for循环依次寻找第0,1,2类页面,如果找到符合条件的页面,victim的值被置为i,即符合条件的页面号,此页面将被置换掉,同时将find变量置为true,之后的循环便不再执行。
当三个for循环执行完后,仍未找到符合条件的页面,那么所有页面都属于第3类,victim的值是默认的0,即第一个页面。
在实现这个算法的过程中,我曾考虑过用两个循环来实现,第一个循环检查每一类中是否有符合条件的页面,第二个循环在最小类中寻找一个符合条件的页面,由于在第一个循环中,无论编号较小的类中是否有页面,循环都必须继续,平均代价比三个for循环更大,所以最终决定用三个for循环来实现。
(2)SC算法的实现:
SC算法只是对FIFO算法进行简单的修改:
检查最老页面的use字段,如果为false,就置换此页面;如果为true,就将它置为false,并将此页面放到FIFO列表的末端。
具体实现是在makeFreeFrame中switch语句的casePAGEREPL_SC中加入:
int*ptr=fifoList->Remove();
while(coreOwners[*ptr]->use)
{
coreOwners[*ptr]->use=false;
fifoList->Append(ptr);
ptr=fifoList->Remove();
}
victim=*ptr;
if(ptr!
=NULL)
deleteptr;
我们用fifoList->Remove()取出FIFO链表头的页面号,即最老的页面号,用一个int指针指向该页面号,并判断该页面的use字段,如果为true,就将它置为false,并就调用fifoList->Append(ptr)函数将此页面号插入到FIFO链表的末端,继续取下一个链表头的页面号;如果为false,就将此页面号赋给victim,将此页面置换掉。
最后将ptr指针指向的内存区域清空。
(3)CLOCK算法的实现:
Clock算法和SC算法的区别仅是实现上的不同,它将所有页面保存在一个类似钟面的环形链表中,用一个指针指向最老的页面,以减少SC算法在链表中移动页面的代价。
我们用coreOwners数组和求模操作来模拟环形链表,用一个clock_hand的正型变量来模拟表针。
具体实现是在makeFreeFrame中switch语句的casePAGEREPL_CLOCK中加入:
while(coreOwners[clock_hand]->use)
{
coreOwners[clock_hand]->use=false;
clock_hand=(clock_hand+1)%NumPhysPages;
}
victim=clock_hand;
clock_hand=(clock_hand+1)%NumPhysPages;
clock_hand的值初始化为0,我们先判断第一个页面的use字段,如果true,表示它最近被使用到,将use设为false,并将clock_hand的值置为(clock_hand+1)%NumPhysPages,进入下一次循环。
这里的求模操作是一个关键,它保证了clock_hand的值在0到NumPhysPages之间循环,类似于一个表针。
如果clock_hand所指的页面use字段为false,循环结束,将victim的值赋为clock_hand,此页面将被置换掉,并将表针向前移动一格,也就是将clock_hand的值设为(clock_hand+1)%NumPhysPages。
(4)WorkingSet算法的实现:
这个算法中,我们认为最早被访问的那个页面不在工作集中,将它置换出去,也就是说,我们要在内存中寻找最后一次访问时间最小的那个页面。
页面中与最后访问时间相对应的是timeStamp字段,每次访问一个页面时,timeStamp都会被更新为访问时的时间。
具体实现是在makeFreeFrame中switch语句的casePAGEREPL_WS中加入:
for(inti=0;i!
=NumPhysPages;i++)
{
if(coreOwners[i]->timeStamp{
v_timestamp=coreOwners[i]->timeStamp;
victim=i;
}
}
v_timestamp的初始值是当前系统时间,所有页面的最后访问时间都小于当前系统时间,所以可以将v_timestamp作为一个临时变量,在遍历所有页面的时候储存最小的最后访问时间,我们用一个for循环遍历内存中的所有页面,并将每个页面的timeStamp与v_timestamp比较,如果小于v_timestamp,就将v_timestamp的值设为当前页面的timeStamp,并将victim的值置为当前页面号。
这样,当遍历完所有页面时,v_timestamp值为所有页面的timeStamp的最小值,而victim的值也恰好是最小timeStamp值对应的页面,此页面将被置换出去。
(5)Aging算法的实现:
在此算法中,每一个内存页面都分配了一个特定位数的计数器,每次时钟中断时,将所有计数器值右移一位,然后将计数器的最高位设为R位的值,发生页面失效时,淘汰计数器值最小的页面。
用一个无符号整形变量来表示一个计数器,无符号整形数组history的每一项代表一个页面的计数器,我们需要在调入页面的时候初始化计数器的值,具体实现是在pageIn的switch语句的casePAGEREPL_AGING中加入:
history[physFrame]=bitmask;
整形变量bitmask的值为(1<因为我们要置换的是计数器值最小的页面,所以在页面刚刚调入时,我们必须将其计数器的值初始化为最大值,如果初始化为0,有可能出现页面刚刚被调入,又立刻被置换出去的情况,不符合要求。
每次时间中断时,我们必须将计数器右移一位,将指定位数的计数器最高位设为页面的R位,即use字段,并将所有页面的use字段设为false。
具体实现是在doUpdations的witch语句中的casePAGEREPL_AGING加入:
for(i=0;i!
=NumPhysPages;i++)
{
if(coreFreeMap->Test(i))
{
history[i]=history[i]>>1;
if(coreOwners[i]->use)
{
history[i]=history[i]|(1<<(hbits-1));
coreOwners[i]->use=FALSE;
}
}
}
我们用一个for循环遍历内存的每个页面。
在对计数器修改之前,要先判断页面是否为空,如果为空,我们并不需要修改它的计数器。
如果不为空,我们用history[i]=history[i]>>1将其计数器的值右移一位。
然后,我们继续判断该页面的use字段,如果为false,无需改变,因为右移一位后,最高位