完整源码见本人博客下载资源
Nachos是采用页式内存分配方式管理用户内存空间的,最主要的两部分,一是AddrSpace类中存了属于该进程的页表TranslationEntry pageTable,并完成了页表初始化,内存空间分配;二是machine类中,ExceptionType Translate(int virtAddr, int physAddr, int size,bool writing);函数模拟了mmu的工作,每次读写内存时,通过页表找到物理地址。
原始的Nachos,物理地址等于虚拟地址,内存只能为一个用户进程服务,必须将用户进程所需空间全部分配内存。
在nachos/userprog/addrspace.cc文件末尾添加如下代码
void
AddrSpace::Print()
{
printf("spaceID: %d\n",spaceID);
printf("page table dump: %d pages in total\n", numPages);
printf("============================================\n");
printf("\tVirtPage, \tPhysPage,\tvalid,\tdirty,\tuse\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");
}
要实现多道程序,就需要有一个全局管理,来组织空闲主存,分配主存。之前在fileSystem中用过的BitMap很适合。
在system.cc中做适当声明和初始化。
其中bool ProgMap[NumPhysPages]数组,是为了分配给每个用户进程唯一标识的spacID(后面会提到)。
#ifdef USER_PROGRAM // requires either FILESYS or FILESYS_STUB
Machine *machine; // user program memory and registers
//**********************
BitMap *freeMM_map;
bool ProgMap[NumPhysPages]; //to set pid
#endif
#ifdef USER_PROGRAM
machine = new Machine(debugUserProg); // this must come first
//*******************************************************************************
freeMM_map = new BitMap(NumPhysPages);
bzero(ProgMap,NumPhysPages); //0
#endif
给类AddrSpace增加int spaceID;属性
初始化时,分配SpacID,如下
AddrSpace::AddrSpace(OpenFile *executable)
{
bool flag = false;
for(int i = 0; i < NumPhysPages; i++)
{
if(!ProgMap[i]){
ProgMap[i] = 1;
flag = true;
spaceID=i;
break;
}
}
ASSERT(flag);
// first, set up the translation
pageTable = new TranslationEntry[numPages];
for (i = 0; i < numPages; i++) {
pageTable[i].virtualPage = i; // for now, virtual page # = phys page #
//*************************************************
pageTable[i].physicalPage = freeMM_map->Find();
pageTable[i].valid = TRUE;
pageTable[i].use = FALSE;
pageTable[i].dirty = FALSE;
pageTable[i].readOnly = FALSE; // if the code segment was entirely on
// a separate page, we could set its
// pages to be read-only
}
// zero out the entire address space, to zero the unitialized data segment
// and the stack segment
//bzero(machine->mainMemory, size);
将code和initData载入内存的部分也要修改,(原先是直接用虚拟地址访问内存)
并在AddrSpace(OpenFile *executable)的末尾调用Print()
/ 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);
//code start from this page
int code_page = noffH.code.virtualAddr/PageSize;
//calculate physical address using page and offset (0);
int code_phy_addr = pageTable[code_page].physicalPage *PageSize;
//read memory
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);
//data start from this page
int data_page = noffH.initData.virtualAddr/PageSize;
//first data's offset of this page
int data_offset = noffH.initData.virtualAddr%PageSize;
//calculate physical address using page and offset ;
int data_phy_addr = pageTable[data_page].physicalPage *PageSize + data_offset;
//read memory
executable->ReadAt(&(machine->mainMemory[data_phy_addr]),
noffH.initData.size, noffH.initData.inFileAddr);
}
Print();
至此,nachos已经可以支持多道程序
按照二、中的原理分析,Exec的系统调用接口已经定义好,我们主要工作是在exception.cc中完成对SC_exec的系统调用的处理工作。
SpaceId Exec(char *name);
exception.cc修改
mipssim.cc的Machine::OneInstruction(Instruction *instr)对系统调用的软中断,处理后是return,所以系统调用处理完后需要完成“pc+1”的工作,否则用户程序无法继续执行。
AdvancePC()完成“PC+1”
ExceptionHandler(ExceptionType which)中增加SC_Exec的选项,并将中断处理交给interrupt类。
//**********************************pc+1
void AdvancePC() {
machine->WriteRegister(PrevPCReg, machine->ReadRegister(PCReg));
machine->WriteRegister(PCReg, machine->ReadRegister(NextPCReg));
machine->WriteRegister(NextPCReg, machine->ReadRegister(NextPCReg) + 4);
}
//****************************************
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 {
printf("Unexpected user mode exception %d %d\n", which, type);
ASSERT(FALSE);
}
}
Interrupt增加void Interrupt::Exec()函数
工作和StartProcess()一样,还需要将当前线程送入就绪队列,而且不能直接fork(StartProcess,1),因为Exec需要返回SpaceID,所以new AddrSpace的工作必须在Exec()中完成,如果用了fork,StartProcess()中会再new一次,这样会有两个SpacID,不唯一,所以不行。
void
Interrupt::Exec()
{
printf("Execute system call of Exec()\n");
//read argument
char filename[50];
int addr=machine->ReadRegister(4);
int i=0;
do{
machine->ReadMem(addr+i,1,(int *)&filename[i]);//read filename from mainMemory
}while(filename[i++]!='\0');
printf("Exec(%s):\n",filename);
//yunxing
OpenFile *executable = fileSystem->Open(filename);
AddrSpace *space;
if (executable == NULL) {
printf("Unable to open file %s\n", filename);
return;
}
space = new AddrSpace(executable);
Thread *thread = new Thread("forked thread");
//把当前线程送入就绪队列
IntStatus oldLevel = interrupt->SetLevel(IntOff);
scheduler->ReadyToRun(currentThread); // ReadyToRun assumes that interrupts
// are disabled!
(void) interrupt->SetLevel(oldLevel);
currentThread=thread;
thread->space = space;
delete executable; // close file
space->InitRegisters(); // set the initial register values
space->RestoreState(); // load page table register
//return spaceID
machine->WriteRegister(2,space->getSpaceID());
machine->Run(); // jump to the user progam
ASSERT(FALSE); // machine->Run never returns;
// the address space exits
// by doing the syscall "exit"
}
写一个exec.c用于测试
#include "syscall.h"
int
main()
{
int pid;
pid = Exec("../test/halt.noff");
Halt();
}