完整源码见本人博客下载资源
一、完成情况一览
- 了解了Nachos虚拟内存机制,包括翻译物理地址,缺页异常
- 实现了页换入换出,基础的虚拟内存管理,包括解决缺页异常,帧分配,页置换等
二、虚拟内存管理设计
1、缺页异常
如果要访问的数据不在主存中,会引发缺页异常,然后处理异常,如果没有内存空间就选择一个页换出,将缺的这个页换入。这种机制,使得虚拟内存发挥了更大作用,我们可以在物理内存上运行需要更多虚拟内存的程序。
虚实地址转换的流程图,这其中可能引发缺页异常:
2、交换空间
换入换出需要交换空间,这里简单的将运行用户程序时传入的文件名作为AddrSpace的成员变量,将该文件作为交换空间。
3、缺页异常处理
需要主要考虑的是采用什么样的帧分配算法和页面置换算法。
这里采用固定分配+局部置换。
固定分配:不是完全固定,而是max(5,必需页数量+1),意味着,如果用户进程很大,它能分到的帧也会很多,必需页的数量由code和initData的总和决定
局部置换:只在本进程空间内寻找牺牲页。按照noff格式的定义,必需页是在虚拟空间的低地址,从零开始。考虑置换时,我们将保证这一部分页始终在内存中,而只选取其他页置换。暂时是随机置换算法,有机会可以实现二次机会算法。
4、硬件条件
nachos的页表项(TranslationEntry对象)已经具备了所需硬件条件。
有valid,dirty,use位。
三、实现过程
有了实验六系统调用的编写经历,处理缺页异常的过程很简单,主要工作在AddrSpace类的功能添加上。
-
AddrSpace类增加
pulic: int findReplacedPage(); void replacePage(int badVAddr); void writeback(int oldPage); private: TranslationEntry *pageTable; // Assume linear page table translation // for now! int spaceID; unsigned int numPages; // Number of pages in the virtual char *filename; //OpenFile *executable; //整个虚拟内存空间 int necessaryFrames; //初始时,固定分配的最大帧数 // address space };
-
初始化用户空间AddrSpace(char *filename)更改
主要初始化 necessaryFrames变量,完成帧固定分配任务。
//******************************************
necessaryFrames=divRoundUp(noffH.code.size + noffH.initData.size,PageSize);
int numFrames =max(MaxNumPhysPages,necessaryFrames+1);
ASSERT(numFrames <= NumPhysPages && numFrames <= freeMM_map->NumClear()); // check we're not trying
// to run anything too big --
// at least until we have
// virtual memory
DEBUG('a', "Initializing address space, num pages %d, size %d\n",
numPages, size);
// first, set up the translation
pageTable = new TranslationEntry[numPages];
for (i=0;i<numFrames;i++){
pageTable[i].virtualPage = i;
pageTable[i].physicalPage = freeMM_map->Find();
pageTable[i].valid = true;
pageTable[i].use = false;
pageTable[i].dirty = false;
pageTable[i].readOnly = false;
}
for(i;i<numPages;i++){
pageTable[i].virtualPage = i;
pageTable[i].physicalPage = -1;
pageTable[i].valid = false;
}
-
处理页错误中的置换函数部分
int AddrSpace::findReplacedPage() { printf("寻找oldPage换出!\n"); for (int i=necessaryFrames;i< numPages;i++){ if(pageTable[i].valid) return i; } } void AddrSpace::replacePage(int badVAddr) { int newPage=badVAddr/PageSize; int oldPage=findReplacedPage(); printf("页置换!%d换出,%d换入\n",oldPage,newPage); writeback(oldPage); pageTable[oldPage].valid=false; pageTable[newPage].physicalPage=pageTable[oldPage].physicalPage; pageTable[newPage].valid=true; pageTable[newPage].dirty = false; pageTable[newPage].readOnly = false; //read to MM OpenFile *executable = fileSystem->Open(filename); if (executable == NULL) { printf("Unable to open file %s\n", filename); return; } executable->ReadAt(&(machine->mainMemory[pageTable[newPage].physicalPage]),PageSize, newPage*PageSize); delete executable; Print(); } void AddrSpace::writeback(int oldPage) { if(pageTable[oldPage].dirty){ printf("dirty!写回磁盘,spaceId:%d,oldPage:%d\n",spaceID,oldPage); OpenFile *executable = fileSystem->Open(filename); if (executable == NULL) { printf("Unable to open file %s\n", filename); return; } executable->WriteAt(&(machine->mainMemory[pageTable[oldPage].physicalPage]),PageSize,oldPage*PageSize); delete executable; } }
-
页错误异常处理,和增加系统调用流程一样,因为都是中断
void ExceptionHandler(ExceptionType which) { int type = machine->ReadRegister(2); if (which == SyscallException) { switch(type){ case SC_Halt: DEBUG('a', "Shutdown, initiated by user program.\n"); interrupt->Halt(); return; case SC_Exec: interrupt->Exec(); AdvancePC(); return; } } else if(which == PageFaultException){ //缺页异常在这处理 interrupt->PageFault(); } else{ printf("Unexpected user mode exception %d %d\n", which, type); ASSERT(FALSE); } }
在Interrupt类中增加Interrupt::PageFault()成员函数
这里需注意,造成页错误的虚存地址在$BadVAddrReg寄存器中
void Interrupt::PageFault() {// The failing virtual address on an exception int badVAddr= machine->ReadRegister(BadVAddrReg); AddrSpace *space=currentThread->space; space->replacePage(badVAddr); }
-
为展示页置换效果,AddrSpace::Print()函数修改
void AddrSpace::Print() { printf("spaceID: %d\n",spaceID); printf("page table dump: %d pages in total\n", numPages); printf("============================================\n"); printf(" VirtPage, PhysPage, valid, dirty, use\n"); for (int i=0; i < numPages; i++) { printf("\t%d,\t%d,\t%d,\t%d,\t%d\n", pageTable[i].virtualPage,pageTable[i].physicalPage,pageTable[i].valid,pageTable[i].dirty,pageTable[i].use); } printf("============================================\n\n"); }
-
细节:AddrSpace::AddrSpace()构造函数参数修改为文件名,所以,对应地,StartProgress以及Exec系统调用都要做小小的更改
四、结果展示
下面的图片请连成一幅图看