操作系统的作业:内存管理。TLB、LRU、clock
实验内容:
1、阅读代码回答问题
(1) 当前系统下,内存大小Mainmemory是多少?
PageSize=128, NumPhysPages=128, Mainmemory=128*128=2^7*2^7=2^14; 内存大小是4KB
(2) 理解从空间分配,加载代码数据,取指执行的流程
空间分配:调用AddrSpace的构造函数,先为该进程分配页表,然后使用for循环建立虚拟内存和物理内存之间的对应关系。
加载代码数据:这部分功能由AddrSpace类中的load()函数实现。在这个函数中,打开了用户文件,读文件头中的信息,计算所需要的内存大小,再调用divRoundUP()函数计算所需要的页的个数。然后根据文件头中的信息,如果有代码段,写入kernel内核中的主存中,数据段也是同样的道理。
取指执行:初始化CPU寄存器,使得它指向需要指向的第一条指令。由于load()加载的数据是虚拟地址,寄存器读取的也是虚拟地址,所以正式执行前需要进行转换。初始化寄存器是AddrSpace()类中的InitRegister()方法。还需要RestoreState()函数将进程中的页表拷贝到CPU中去维护。最后调用machine中的Run()方法,使用OneInstruction函数模拟一次取指令和执行该指令的操作。
(3) PageTable表中,虚拟地址和物理地址的对应关系如何?Translate函数如何工作。
① Translate()函数有4个参数:
l virtAddr:用户程序的逻辑地址;
l physAddr:转换后的实际地址;
l size:数据类型的大小;
l writing:读/写内存标志
② 判断用户的逻辑地址是否对齐:
l 如果size是2,virtAddr必须是2的倍数;
l 如果size是4,virtAddr必须是4的倍数。
l 没有对齐则返回AddressErrorException。
③ 计算出虚拟地址所在页号v*n以及所在页面的偏移量
④ 采用不同的转换方法做不同的处理:
l 如果采用的是线性转换表:当v*n>=pageTableSize时,虚拟页数过大,返回AddressErrorException;如果页表中显示该页无效,返回PageFaultException;一切正常得到相应的页表表项。
l 如果采用的是TLB转换表:如果查找到了,得到相应的页表表项;如果没有查找到,返回PageFaultException
⑤ 如果得到的页表表项是只读,但是设置的是writing,返回ReadOnlyException
⑥ 如果物理地址大于实际内存物理地址,返回BusErrorException
⑦ 设置表项正在使用标志,如果writing标志设置,设置表项中的dirty标志
⑧ 然后在页表中查找它们所在的物理页框号,计算物理地址。
返回NoException。
(4) 什么情况下会产生PageFault的exception, 是否有对应处理函数?
在Translate()函数中,采用线性转化表时,如果查找的页是无效的,返回PageFaultException;采用TLB表,如果没有查到,返回PageFaultException。Translate()函数在ReadMem()函数中被调用,因而PageFaultException被返回到ReadMem()函数中进行判断,并被传入到RaiseException()函数中,这个函数会调用ExceptionHandler()函数,由它对传入的异常进行处理。
在这个函数 中,由switch语句判断传入的是异常还是系统调用,如果是系统调用再采用一个switch-case语句判断是何种系统调用;如果是异常直接打印出错信息。需要说明的是,在处理完PageFaultException之后,不需要将PC+4。因为处理完异常后,返回的最终位置是OneInStruction函数的取指阶段,取指失败后,OneInstruction会退出,再用相同PC取指令。这个时候就可以命中了。
2. 设计LRU策略下的TLB
(1) 在本次实验中为了减少代码量,设定TLB和PageTable的关系如下所述:
TLB在系统中唯一,所有线程可用;PageTable为每个进程一个;
系统尚未实现真正意义上的虚拟内存管理,系统仅运行一个用户线程;
系统同时有TLB和 PageTable,TLB视为对PageTable中部分代码的高速缓存;
系统开始之初,TLB为空,PageTable 由NachOS代码完成原来的初始化;
系统取指时,地址的翻译由函数Translate()进行,这时从TLB中的页表项查找物理地址和虚拟地址的对应关系;
如果不命中,则产生PageFault异常,转移到异常处理函数进行处理;
该处理函数需要自己写,完成从PageTable加载对应页表项到TLB;
线程切换时,TLB需要清空;
加载到TLB之后,需要回到中断产生的地方,继续执行
(2) 在machine.h .cc中完成TLB的定义和初始化,修改数据结构增加时间记录变量
(3) 完成异常处理函数调用,以LRU方式进行TLB替换;注意必要时同步被替换项回到PageTable;//加分clock算法
(4) 时间信息可以来自stats下的totalTicks(溢出情况不考虑,该值为int,有一定隐患);加入必要的信息,显示缺页异常发生,和TLB哪个页表项在进行替换,替换成哪个页表项
(5) 运行程序观测是否正常?如果不正常请跟踪代码,发现缺页异常处理返回后程序没有重新正常运行;
(6) 修改WriteMem和ReadMem,在缺页异常处理返回后重新调用Translate()函数
答案:
LRU运行截图:
修改的代码:
源码中已经给出了TLB的实现,在machine.h中增加一个宏定义,使得nachos产生一个TLB。
需要注意的是,在Translate.cc中的Translate()函数中有一个判断,意思是TLB和page table不能同时存在,需要将它注释掉。
Translate.h中,在TranslateEntry中增加一个属性lastUseTime。
Exception.cc中。判断TLB缺页,抛出的exception时TranslateFaultException,在switch函数中进行判断,并且判断是不是TLB缺失。调用LRU算法。
在machine.h中,实现TLB的构造,对lastUseTime进行赋值
在machine.cc中实现MyHandleTLB()函数:
在Translate.cc中,修改WtriteMem()函数,发生exception时,先判断是不是PageFaultException,如果是,调用了RaiseException()函数后,缺页已经解决,再次调用Translate()函数。
ReadMem函数中修改方法相同
2. (选做)以CLOCK方式,仅使用use标志位
运行截图:
修改代码:
Translate.h中,在TranslateEntry中增加一个属性useTag
Translate.cc中的Translate()函数以及ReadMem()WriteMem()函数依然和LRU函数中的一样,不需要修改。
Exception.cc中。判断TLB缺页,调用clock算法
Machine.h中,在machine类中增加clock算法处理TLB缺页的函数声明:MyClockTLB(int addr);
Machine.cc中,实现这个函数。
现在文件头上增加一个全局变量cursor,这个是时钟算法中的指针。
在TLB构造函数中,对useTag赋值为0;
下面是对MyClockTLB(int addr)函数的实现