Nachos实习——Lab2虚拟内存实习报告
文章目录
内容一:总体概述
本次实验主要是通过阅读相关代码,了解 nachos用户程序的执行过程,之后完成TLB,页表和虚拟内存等的实现。。其中第一部分主要内容是实现TLB相关异常处理和置换算法,当前的 nachos只支持单个用户程序,没有用到TLB。第二部分的主要内容是实现全局内存管理机制,使得 nachos内存可以同时存在多个线程。第三部分的主要内容是实现程序运行过程中发生缺页中断时,才会将所需的页面从磁盘调入内存。Challenge部分是增加线程挂起状态以及实现倒排页表。
内容二:任务完成情况
Exercise 1 | Exercise 2 | Exercise 3 | Exercise 4 | Exercise 5 | Exercise 6 | Exercise 7 | Challenge | |
---|---|---|---|---|---|---|---|---|
完成情况 | Y | Y | Y | Y | Y | Y | Y | Y |
内容三:具体完成Exercise情况
一、TLB异常处理
目前,Nachos系统对于内存的管理是基于软件模拟的TLB机制。其工作原理、异常处理、替换算法等方面,与分页式内存管理非常相像。
Exercise 1 源代码阅读
- 阅读code/userprog/progtest.cc,着重理解nachos执行用户程序的过程,以及该过程中与内存管理相关的要点。
- 阅读code/machine目录下的machine.h(cc),translate.h(cc)文件和code/userprog目录下的exception.h(cc),理解当前Nachos系统所采用的TLB机制和地址转换机制。
1、userprog/progtest.cc
progtest.cc文件是Nachos系统中和执行用户程序相关的文件。在里面定义了两个函数。其中StartProcess函数是用来加载并执行用户程序的。具体流程如下:
-
首先传入文件名,并打开该文件。之后会对该文件的文件格式进行一个判断。
-
之后通过类 AddrSpace(在userprog/addrspace.cc文件下定义)的构造函数为用户程序分配并初始化地址空间,分配给当前线程/
-
初始化用户寄存器的值,调用 RestoreState函数,将 machine的 pageTable指向该 AddrSpace对象的页表,将页表加载到 machine中
-
调用 machine>Run执行用户程序。
Progtest.cc中还有一个ConsoleTest函数,用于控制台输入输出的测试。
2、machine/machine.cc和machine/machine.h
这两个文件主要用于模拟 nachos用户程序运行的机器。 machine.h中定义了一些内存相关的参数,如内存大小、TLB大小等,还有一些异常的种类和一些特殊用户寄存器的编号。还定义了两个类 Instruction和machine。类 Instruction用于将目标代码转为mips可执行的代码。Machine类模拟机器的运行。此外还有一个外部函数用来进行异常处理的。其中TLB的初始化是在machine/machine.cc文件中
目前默认是没有使用的,如需使用需要在userprog/Makefile文件中添加宏。
3、machine/translate.h(cc)和userprog/exception.h(cc)
在translate头文件中定义了页表TranslationEntry类,包含物理页号、虚拟页号、有效位、访问位、只读位、更改位等属性。translate.cc文件中实现了machine的读写内存和地址转换功能。地址转换有两种情况,使用线性页表或使用TLB。目前没有使用tlb所以直接跳转到tlb=null下面执行。在执行地址转换过程中可能会抛出各种异常。而异常的处理则使用userprog/exception.h(cc)文件下的ExceptionHandler函数。
通过阅读以上文件,我画了一个用户程序执行图。
Exercise 2 TLB MISS异常处理
修改code/userprog目录下exception.cc中的ExceptionHandler函数,使得Nachos系统可以对TLB异常进行处理(TLB异常时,Nachos系统会抛出PageFaultException,详见code/machine/machine.cc)
1、设计思路
首先Translate()方法,启动TLB,让用户程序在运行的时候先访问 TLB,如果出现 TLB MISS,会立刻抛出一个 RaiseException(),然后通过 ExceptionHandler()处理这个缺页异常,处理的动作就是让系统从pageTable 页表中查找要找的页表项。
2、userprog/Makefile
我们需要使用TLB,但是TLB并没有启用(在Exercise1里面解释过),所以我们需要先在userprog/Makefile添加宏
3、machine/translate.cc
因为系统是默认没有启用TLB的,但是我们现在启用了TLB,所以我们应该注释掉ASSERT(tlb == NULL || pageTable == NULL);
,否则会报错Assertion failed: line 203, file "../machine/translate.cc"
4、userprog/exception.cc
修改ExceptionHandler函数,因为之前系统是没有PageFaultException异常的(因为之前系统默认是把物理页面全部装入内存的,也没有启用TLB所以不会出现PageFaultException),所以我们需要添加PageFaultException,并进行处理。
else if(which == PageFaultException){
if (machine->tlb == NULL) {
//页表失效,因为默认不会出现所以直接使用ASSERT(FALSE);
ASSERT(FALSE);
} else {
//快表失效,处理流程是首先调用 machine的ReadRegister函数,从BadVAddrReg寄存器中取出发生异常的虚拟地址,并算出vpn。然后确定快表替换的表项,如果快表存在空的表项,那么直接使用空的表项,否则根据特定的规则确定替换的表项。
DEBUG('m', "=> TLB miss (no TLB entry)\n");
int BadVAddr = machine->ReadRegister(BadVAddrReg);
TLBMissHandler(BadVAddr);
}
return;
}
int position = 0;
void TLBMissHandler(int virtAddr)//快表失效处理函数
{
unsigned int vpn;
vpn = (unsigned) virtAddr / PageSize;
machine->tlb[position] = machine->pageTable[vpn];
//下面的Exercise3才实现了具体快表置换算法,这里为了简化测试,只使用了2个size的TLB
if(position==0)
position = 1;
else
position = 0;
}
5、测试结果截图
Exercise 3 置换算法
为TLB机制实现至少两种置换算法,通过比较不同算法的置换次数可比较算法的优劣。
1、FIFO算法
算法的思想是每次淘汰最先进入TLB的页面。具体实现方式则是每次移除块表数组的第一项,然后一次将后面的往前移,新的表项放在快表数组的尾项。
userprog/exception.cc
void TLBAlgoFIFO(int virtAddr)
{
int position1 = -1;
unsigned int vpn;
vpn = (unsigned) virtAddr / PageSize;
//寻找空的TLB数组
for (int i = 0; i < TLBSize; i++) {
if (machine->tlb[i].valid == FALSE) {
position1 = i;
break;
}
}
// 如果满了,移除首项,然后把每一项往前移动,然后放在最后一项
if (position1 == -1) {
position1 = TLBSize - 1;
for (int i = 0; i < TLBSize - 1; i++) {
machine->tlb[i] = machine->tlb[i+1];
}
}
//更新TLB
machine->tlb[position1] = machine->pageTable[vpn];
}
2、CLOCK时钟置换算法
Nachos系统已经定义了TLB的use和valid,所以我们可以很方便的实现时钟算法。具体实现是首先判断valid的值,看该位置是否被访问过,如果为false则直接进行替换;如果为true,则进一步判断use的值,来看是否被修改过,如果修改过则将其值置为false,然后判断下一位。如果为use为false则直接进行替换.
int position3 = 0;
void TLBAlgoClock(int virtAddr)
{
//寻找那个use和valid都为0的位置,选取的顺序为(0,0)->(0,1)->(1,0)->(1,1)
unsigned int vpn;
vpn = (unsigned) virtAddr / PageSize;
while (1) {
position3 %= TLBSize;
if (machine->tlb[position3].valid == FALSE) {
break;
} else {
if (machine->tlb[position3].use) {
//更新use的值
machine->tlb[position3].use = FALSE;
position3++;
} else {
break;
}
}
}
//更新TLB
machine->tlb[position3] = machine->pageTable[vpn];
machine->tlb[position3].use = TRUE;
}
3、测试两个算法,并打印出TLB相关信息
首先在translate.cc中设置两个全局变量, TLBMissCount = 0;(记录TLB MISS);TranslateCount = 0(记录进程页面访问次数)。并在machine.h中进行扩展声明。
然后在每次发生PageFaultException让TLBMissCount+1;在每次执行TranslateCount函数时,让TranslateCount+1。分别调用两个算法最终在程序执行结束退出后调用debug函数打印出TLB缺页次数,缺页率等信息。
void
ExceptionHandler(ExceptionType which)
{
if ((which == SyscallException) && (type == SC_Halt)) {
// 在程序执行结束后打印TLB信息
DEBUG('T', "TLB Miss: %d, TLB Hit: %d, Total Translate: %d, TLB Miss Rate: %.2lf%%\n",
TLBMissCount, TranslateCount-TLBMissCount, TranslateCount, (double)(TLBMissCount*100)/(TranslateCount));
} else if(which == PageFaultException){
//发生缺页终端则让TLBMissCount++
TLBMissCount++;
if (machine->tlb == NULL) {
// linear page table page fault
……………………
} else {
//TLBMissHandler(BadVAddr);//TLB MISS测试
TLBAlgoFIFO(BadVAddr);//FIFO算法测试
//TLBAlgoClock(BadVAddr);//CLOCK时钟算法测试
}
return;
}
}
记得在translate函数中让TranslateCount++
4、测试结果
测试说明:我试用了系统提供的sort排序进行测试,在最开始的时候报错
Assertion failed: line 81, file "../userprog/addrspace.cc
,仔细阅读代码发现是因为系统给的sort排序超出了内存限制,同时原来sort在最后接触进行的系统调用EXIT 尚未在本系统中实现,所以我在原来的基础上进行了修改,并重新make.