说明:blog 中写到的这几个实验,不全面而且也不是上交实验报告的最终版本(是自己实验过程中用typora简单记录的笔记),完整内容(含代码+实验报告)可以通过(山东大学软件学院操作系统课设)下载,或者微信公众号关注“陌兮blog”免费获取
结合实验指导书,实现nachos虚拟内存的过程大概可以分为:处理缺页异常、页置换、帧分配这三个部分。
虚拟内存技术可以在带有分页式内存管理的系统中,通过采用请求式分页存储管理技术实现。其关键的技术是,当请求的页不在物理内存时使用缺页异常或自陷进入内核,由内核缺页异常处理程序从外存将该页装入一空闲内存帧中,如果无空闲帧,将选择已在物理内存中的一页将其置换。在这一异常被处理完成之后,发出对该内存访问的同一条指令将再次被执行。
Nachos 提供了实现虚拟内存技术的基本机制,可以通过这些基本机制来实现 Nachos 中的虚拟内存。其中缺页异常是由 MIPS 机模拟函数 translate 产生的。
从该部分源码可以看出,参数 addr 是一条指令发出的要寻找的逻辑地址。这个地址通过调用函数 translate 被转换为物理地址。如果发生缺页异常,则会通过调用函数 RaiseException 将该异常分派到对应的异常处理函数处理。
最后 RaiseException 调用 ExceptionHandler 处理,和实验六一样,我们首先需要做的就是在 ExceptionHandler 中加上处理缺页异常的分支。
页置换是解决当物理内存已满且有一新页需要装入时怎样选择被淘汰页的问题。
Nachos 对于页置换的硬件模拟的支持反映在 machine/translate.h 文件中定义的页表结构 TranslationEntry 中:
变量 valid 和 readonly 对应 MMU 硬件的 valid 和 read-only 位,是用于内存管理和保护的。变量 use 和 dirty 对应硬件的 use 位和 dirty 位,是用于虚拟内存页置换的。
有效位:valid 表示相关的页在进程的逻辑地址空间内,同时在内存中,可以访问。反之不可以访问(不合法或者在磁盘上)。
在 Nachos 的 MIPS 模拟器中的物理内存是通过一个字节型的数组模拟的。在文件 machine/machine.cc 中定义的 Machine 类的构造函数初始化了这个基本内存,其大小为 Memeorysize 字节或 NumphysPages 帧。
在文件 machine.h 中可以看到 NumPhysPages 当前定义的大小是 32
首先实现缺页异常处理函数,在实验六中,我们已经熟悉了nachos的异常处理的相关机制,所以这里与实验六的实现方法类似。
首先在 exception.cc 中增加 else if 分支,通过调用 interrupt 的 PageFault() 处理缺页异常
然后再实现 PageFault()。首先在 interrupt.h 中定义类方法
然后在 interrupt.cc 中实现函数
//lab-7 add by lhw************************************
void
Interrupt::PageFault()
{
//从寄存器中读取想换入的页
int badVAddr= machine->ReadRegister(BadVAddrReg);
AddrSpace *space=currentThread->space;
//调用FIFO函数实现页的置换
space->FIFO(badVAddr);
}
//lab-7 add by lhw************************************
接下来实现 FIFO 函数
在 AddrSpace 类增加成员函数,在文件 addspace.h 中定义
void FIFO(int badVAdrr);
然后在 addrspace.cc 中实现函数
//lab-7 add by lhw**************************
void
AddrSpace::FIFO(int badVAddr)
{
int newPage=badVAddr/PageSize;
printf("需换入newPage: %d\n",newPage);
int oldPage = virtualMem[p_vm];
printf("使用FIFO确定换出页oldPage: %d\n",oldPage);
virtualMem[p_vm] = newPage;
p_vm = (p_vm+1)%MaxNumPhysPages;
printf("进行FIFO页置换:%d页换出,%d页换入\n",oldPage,newPage);
//写回
writeBack(oldPage);
pageTable[oldPage].valid = false;
pageTable[newPage].physicalPage = pageTable[oldPage].physicalPage;
pageTable[oldPage].physicalPage = -1;
pageTable[newPage].valid = true;
pageTable[newPage].dirty = false;
pageTable[newPage].readOnly = false;
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();
}
replacePage 函数调用 writeBack 函数实现把换出的页写回。
实现 writeback 函数,同样是 addrspace.h 定义
void writeBack(int oldPage);
addrspace.cc 实现
//先通过dirty为判断是否已经改动过,如果true,写回磁盘
void
AddrSpace::writeBack(int oldPage)
{
if(pageTable[oldPage].dirty){
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;
}
}
然后在 AddrSpace 类中添加几个必要的成员变量
//lab-7 add by lhw************************
unsigned int StackPages;
char *filename;
//for FIFO
int virtualMem[MaxNumPhysPages]; //FIFO页顺序存储
int p_vm; //FIFO换出页指针
//lab-7 add by lhw************************
对 AddrSpace 的构造函数进行修改
//lab-7 modify by lhw*****************************
AddrSpace::AddrSpace(char *filename)
{
//lab-6 add by lhw***************************
//初始化时,遍历寻找未分配的spaceID,然后分配spaceID
bool flag = false;
for(int i = 0; i < 128; i++)
{
if(!ThreadMap[i]){
ThreadMap[i] = 1;
flag = true;
spaceID=i;
break;
}
}
ASSERT(flag);
//lab-6 add by lhw*************************
//lab-7 add by lhw*************************
OpenFile *executable = fileSystem->Open(filename);
if (executable == NULL) {
printf("Unable to open file %s\n", filename);
return;
}
this->filename=filename;
//lab-7 add by lhw**************************
NoffHeader noffH;
unsigned int i, size;
executable->ReadAt((char *)&noffH, sizeof(noffH), 0);
if ((noffH.noffMagic != NOFFMAGIC) &&
(WordToHost(noffH.noffMagic) == NOFFMAGIC))
SwapHeader(&noffH);
ASSERT(noffH.noffMagic == NOFFMAGIC);
// how big is address space?
//lab-7 add by lhw***************************
StackPages = divRoundUp(UserStackSize,PageSize);
numPages = divRoundUp(noffH.code.size,PageSize) + divRoundUp
(noffH.initData.size,PageSize) + divRoundUp(noffH.uninitData.size,PageSize)+ StackPages;
size = (MaxNumPhysPages+StackPages)*PageSize;
printf( "numPages = %d \n" ,numPages );
//lab-7 add by lhw***************************
//lab-6 add by lhw***************************
//检查所需空间大小
ASSERT(numPages <= NumPhysPages && numPages <= bitmap->NumClear());
//lab-6 add by lhw***************************
DEBUG('a', "Initializing address space, num pages %d, size %d\n",
numPages, size);
// first, set up the translation
//lab-7 add by lhw*************************
initPageTable();
//lab-7 add by lhw*************************
// zero out the entire address space, to zero the unitialized data segment
// and the stack segment
//lab-6 add by lhw*************************
//bzero(machine->mainMemory, size);
//lab-6 add by lhw*************************
// then, copy in the code and data segments into memory
if (noffH.code.size > 0) {
DEBUG('a', "Initializing code segment, at 0x%x, size %d\n",
noffH.code.virtualAddr, noffH.code.size);
int code_page = noffH.code.virtualAddr/PageSize;
int code_phy_addr = pageTable[code_page].physicalPage *PageSize;
executable->ReadAt(&(machine->mainMemory[code_phy_addr]),
noffH.code.size, noffH.code.inFileAddr);
}
if (noffH.initData.size > 0) {
DEBUG('a', "Initializing data segment, at 0x%x, size %d\n",
noffH.initData.virtualAddr, noffH.initData.size);
int data_page = noffH.initData.virtualAddr/PageSize;
int data_offset = noffH.initData.virtualAddr%PageSize;
int data_phy_addr = pageTable[data_page].physicalPage *PageSize + data_offset;
executable->ReadAt(&(machine->mainMemory[data_phy_addr]),
noffH.initData.size, noffH.initData.inFileAddr);
}
//lab-7 add by lhw**********************
delete executable;
//lab-7 add by lhw**********************
Print();
}
AddrSpace::AddrSpace() 构造函数参数由 executable 修改为 char* filename,所以对应地 StartProgress 以及Exec 系统调用都要更改
progtest.cc:
interrupt.cc:
然后在 AddrSpace::AddrSpace() 中,调用了 initPageTable() 函数实现页表的初始化,接下来在 addrspace.h 定义
void initPageTable(); //页表初始化
在 addrspace.cc 中实现
void
AddrSpace::initPageTable(){
int i=0;
int numFrames =MaxNumPhysPages;
p_vm = 0;
pageTable = new TranslationEntry[numPages];
for (i=0;i<numFrames;i++){
pageTable[i].virtualPage = i;
pageTable[i].physicalPage = bitmap->Find();
pageTable[i].valid = true;
pageTable[i].use = false;
pageTable[i].dirty = false;
pageTable[i].readOnly = false;
virtualMem[p_vm] = pageTable[i].virtualPage;
p_vm = (p_vm+1)%MaxNumPhysPages; //向前移一位
}
for(i;i<numPages;i++){
pageTable[i].virtualPage = i;
pageTable[i].physicalPage = -1;
pageTable[i].valid = false;
}
}
然后再修改一下 AddrSpace 的析构函数,增加对有效位的判断
最后修改 Print 函数,便于测试。增加了 valid、dirty、use 的输出。
//lab-7 modify by lhw********************
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");
}
//lab-7 modify by lhw**********************
可以用耗费用户内存比较多的 code/test/sort.c 作为虚拟内存的用户测试程序。但需要改两个地方。一是 ARRAYSIZE 原来定义的值 1024 太大,以至于程序频繁进行换页,导致输出刷屏太厉害,所以这里需要改小。二是最后的 Exit 系统调用还没有实现,所以可暂时简单地改为 Halt 系统调用。
在 test 和 lab7 目录下分别 make 编译后,在 lab7 目录下执行 sort.noff ,观察屏幕输出结果
输出结果: