改造nachos内核以支持多道程序4(转)
改造nachos内核以支持多道程序
一、实验目的
本次实验的目的在于改造nachos内核以支持多道程序。实验内容分四部分:实现新的内存管理模块以完成基本分页管理;实现Exec系统调用;实现Exit和Join系统调用(选做);编写简单的用户态测试程序以测试新改造的多道程序环境(详细内容请看nachos-labs.pdf)。
二、实验内容
2.1 实现新的内存管理模块以支持基本分页管理
系统已经实现了分页管理的硬件支持——页表和地址变换机构,请参考machine/translate.cc和machine/translate.h
目前的系统仅支持单道程序,因此尽管使用分页地址变换机构来进行地址重定位,但内存的分配和回收却采用单一连续分配。这点主要体现在userprog/addrspace.cc里AddrSpace::AddrSpace的实现。该实现中一旦创建新的用户程序,就从用户内存(实际上是一个字符数组machine->mainMemory,见machine/machine.h和machine/machine.h)地低端开始连续读入相应的内存映像。
我们要做的是改变这种局面,实现离散的内存分配和回收。
需要扩充的一点是:对用户内存分页框管理,引入某种机制对各个页框的使用情况进行登记。可以使用bitmap机制(userprog/bitmap.cc和userprog/bitmap.h)也可以如nachos-labs.pdf提示的那样使用lab2里所实现的Table机制。这部分代码请放在userprog/memorymanager.cc和userprog/memorymanager.h中
为了实现离散存储分配,注意在导入用户程序内存映像时应注意逐页导入。为了处理内存不足的错误,请将原来AddrSpace::AddrSpace里的代码改写后剥离出来成为AddrSpace的一个公共方法(请修改userprog/addrspace.cc和userprog/addrspace.h的相关部分)。
请修改userprog/progtest.cc的StartProcess函数以适应新的修改。并直接用ttest/halt进行测试(让nachos执行用户程序的步骤见2.4)。
2.2 实现Exec系统调用
Exec是一个系统调用,因此首先需要阅读的是userprog/syscall.h,从中可以知Exec对应的系统调用号和该调用的原型。
系统调用的入口在userprog/exception.cc中,ExceptionHandler函数将处理是异常处理和系统调用的总入口。目前仅包含对Halt系统调用的处理。我们所要添加的代码应从此函数引出(可使用switch进行分支)。
Exec系统调用需要完成的工作是:根据给定的可执行文件名创建一个进程,并让进程执行该程序。
需要处理的第一件事是:获得可执文件名。请考虑如下几个问题:
当用户程序执行Exec系统调用时,文件名参数如何传递给内核?(请阅读userprog/exception.cc关于ExceptionHandler的注释)
作为参数的字符串首地址是否能直接用?请注意区分逻辑地址与物理地址、用户空间与内核空间的差别。值得强调的是:用户空间现在使用的是machine->mainMemory,内核空间则直接使用系统内存。
各种出错情况都需要考虑,如:文件不存在、文件名超长等。
nachos里的进程至少应包含一个线程。线程序才是调度的单位。
理解nachos里进程和线程的区别:(见threads/thread.h)
进程比线程多了一个地址空间,包含用户空间程序
进程比线程多了一组寄存器的值
进程比线程多了两个分别用于保存和恢复寄存器值的方法
userprog/progtest.cc里的StartProcess函数提供了一些参考。但在参考时应该注意
Machine::Run方法用于逐条执行进程的用户程序。
目前的实现中仅考虑单道程序,因而直接设置currentThread->space。如果是多道程序还能这么做吗?
Exec的返回值是进程地址空间的id(0表示错误),因而必须维护一个表以管理所有的进程。可以使用lab2里实现的Table。另外,请注意一个细节,如何设置Exec的返回值?(请阅读userprog/exception.cc关于ExceptionHandler的注释)
值得特别注意的是,系统调用服务执行完毕后,硬件的指令计数器并没有递增。(见machine/mipssim.cc里的Machine::OneInstruction方法的实现)这将导致相应的系统调用被一再执行!可以通过添加如下函数并在系统调用执行完毕后调用该函数而实现相应计数器的递增:
void AdjustPCRegs() { int pc; pc = machine->ReadRegister(PCReg); machine->WriteRegister(PrevPCReg, pc); pc = machine->ReadRegister(NextPCReg); machine->WriteRegister(PCReg, pc); pc += 4; machine->WriteRegister(NextPCReg, pc); }
在正式实现Exit系统调用之前,也必须对Exit调用进行处理。可以简单地结束当前线程的执行(调用currentThread->Finish())。否则,无法正常对Exec的代码实现是否正确进行测试。
2.3 实现Exit和Join系统调用
Exit调用应该能回收大部分相应进程的资源、记录退出状态并唤醒可能的等待者(调用了Join的父进程)
Exit的代码应该实现为一个函数。这样,很多系统调用的出错处理都方便地调用之。
Join调用后,相应进程将等待直到指定的子进程退出。
几个问题值得考虑:
Exit的退出状态放在什么地方?
如果子进程先退出,父进程再调用Join,则如何处理。
如果父进程没有调用Join则会不会有子进程的资源没有释放?
2.4关于建立和执行新的测试程序
阅读test/Makefile,了解test目录下的.c文件是如何建立成能在nachos(MIPS的模拟平台)中运行的可执行版本。
要建立新的测试程序可以遵循如下步骤(以mytest.c为例):
编辑该测试程序,并保存在test目录中
修改test/Makefile里的关于目标all的规则,在all的前提条件里增加mytest
在test/Makefile增加两条新的规则如下:
mytest.o: mytest.c $(CC) $(CFLAGS) -c mytest.c mytest: mytest.o start.o $(LD) $(LDFLAGS) start.o mytest.o -o mytest.coff ../bin/coff2noff mytest.coff mytest
在test目录下键入make命令
编写nachos系统的用户程序时应注意
标准c的函数如:prinf,scanf等都不能用。
应使用c的语法。
如果觉得需要用户级的调试信息输出,可以考虑添加一个相应的系统调用。
执行测试程序请使用userprog/下的nachos并带上-x参数。如:
chenyd@cs8:~/nachos/nachos-3.4/code/userprog$ ./nachos -x ../test/halt
需要编写的两个测试程序的要求请参考nachos-labs.pdf的p29
三、实验思路
3.1 实现新的内存管理模块以支持基本分页管理
通过对AddrSpace::AddrSpace的改写予以实现,nachos本身提供的虚存与实存的对应机制是一一对应的pageTable[i].physicalPage = i;我们所要做的就是将其改编成离散的内存分配和回收pageTable[i].physicalPage = memM->nextFree();并将AddrSpace::AddrSpace里的代码剥离出来成为AddrSpace的一个公共方法,改写代码如下:
将AddrSpace::AddrSpace剥离:
AddrSpace::AddrSpace(OpenFile *executable) { execFile = executable; }
新建方法AddrSpace::NewPages(),将改写后的AddrSpace::AddrSpace放在其中,代码如下:
if (memM->numFree() < numPages) { cout << "-- Not enough pages for process!!" << endl; cout << "-- Current freePageNum : " << memM->numFree() << " ,the physica page needed: " << numPages << endl; Exit(-127); return; } pageTable = new TranslationEntry[numPages]; for (i = 0; i < numPages; i++) { pageTable[i].virtualPage = i; // for now, virtua page # != phys page # // pageTable[i].physicalPage = memM->nextFree(); /*cout << " virtualPage: " << i << " physicalPage: " << pageTable[i].physicalPage << endl;*/ 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 } // then, copy in the code and data segments into memory (page by page) int codeSize = noffH.code.size + noffH.initData.size; char *buffer = new char[codeSize + 4]; // assume that int = 4 bytes execFile->ReadAt(buffer, codeSize, noffH.code.inFileAddr); // int offset, page; int vaddr = noffH.code.virtualAddr; for (int j=0; j < codeSize; j++) { page = (int)(vaddr / PageSize); offset = vaddr % PageSize; /* cout << "vaddr :" << vaddr << " page : " << page << " offset : " << offset << endl;*/ machine->mainMemory[pageTable[page].physicalPage*PageSize + offset] = buffer[j]; vaddr++; }
编写memorymanager.cc和memorymanager.h实现对用户内存分页框管理,对各个页框的使用情况进行登记。Memorymanager.h的类定义如下:
class MemoryManager { public: MemoryManager(); //构造函数,初始化pageTable ~MemoryManager(); void mark(int pageNum); // allocate//标记哪个页已经被进程占有 void free(int pageNum); // free a frame//清空内存中的所有页 void busy(int pageNum); // in using ? void print(); // current states of the allcated frames//打印被占的页号 boo test(int pageNum); // dirty or not//测试which页是否是赃页 int nextFree(); // find a free frame, and mark it //寻找一个空页 int numFree(); // number of tota free framse//总空闲页数 private: BitMap * bitTable; //通过pageTable实现bitmap机制 };
通过memorymanager.cc将memorymanager.h中的类接口和bitmap一一对应。
然后同实验一一样改写makefile文件,在usrprog目录下make并键入./nachos –x ../test/halt运行。在system.cc里面实例化了一个MemoryManager类的memM对象,提供对整个系统的内存分页分配和回收管理。
3.2 实现Exec系统调用
作为参数的字符串首地址能不能直接使用。通过读取寄存器来读入数据
对于有参数的系统调用,MIPS编译决定了参数传递的以下规则:
参数1: r4寄存器
参数2: r5寄存器
参数3: r6寄存器
参数4: r7寄存器
如果系统调用有返回值,根据MIPS的标准C调用习惯,返回值存放在r2寄存器中。
于是改写exception.cc文件如下:
添加新的系统调用:
case SC_Exec : DEBUG('a', "Exec call\n"); from = machine->ReadRegister(4); for (i = 0; i < MAXLEN; i++) {//将文件名存入fileName[]中,每次读内存一个字节 machine->ReadMem(from, 1, &tmp); fileName[i] = (char)tmp; if ((char)tmp == '\0') break; from++; } if (i == MAXLEN) cout << "the file name must be less than 128 chars!" << endl; spaceId = new_Exec(fileName);//new_Exec()中传的是fileName所以要得到文件名,方法见上,spaceId要保存返回 machine->WriteRegister(2, spaceId);//寄存器存返回值 pM->AdjustPCRegs(); break;
其中通过pM对象进行进程管理,其代码保存在processmanager.cc和processmanager.h中。
Processmanager.h中定义的类如下:
typedef struct processCB { // 进程控制块 int spaceId; int numWait; int parent; int exitCode; Lock_sleep *lock; Condition_sleep *wait; }PCB; class ProcessManager { public: ProcessManager(); ~ProcessManager(); // the process ID table int Alloc(PCB *object); PCB *Get(int spaceId); void Release(int id); void AdjustPCRegs(); // increase the pc after returns// 用于在调用增加PC private: Table *processTable; }; 3.3 实现Exit和Join系统调用 实现这些调用同exec是大同小异的,只需要在exception.cc中添加如下代码: case SC_Exit : DEBUG('a', "Exit call\n"); Status = machine->ReadRegister(4);//new_Exit()中传的是Status故只须从寄存器中取出状态标号即可.也是 new_Exit(Status); pM->AdjustPCRegs(); break; case SC_Join : DEBUG('a', "Join call\n"); id = machine->ReadRegister(4); exitCode = new_Join(id); machine->WriteRegister(2, exitCode); pM->AdjustPCRegs(); break; 要建立新的测试程序可以遵循如下步骤(以mytest.c为例):
3.4编辑该测试程序,并保存在test目录中
编写两个测试函数如下:
tree.c : int main() { int i, j, k, tmp; for (i = 0; i < M; i++) { for (k = 0; k < N*i; k++){ tmp = Exec("../test/run"); Join(tmp); } } return 0; } tree-nojoin.c : int main() { int i, j, k, tmp; for (i = 0; i < M; i++) { for (k = 0; k < N*i; k++){ tmp = Exec("../test/run"); } } return 0; }
四、实验结果
1、新内存分配机制,输入./nachos –x ../test/halt,得到结果:
2、然后测试tree.c的,输入./nachos –x ../test/tree测试结果:
取部分截图如下:
从图中,我们可以很明显的看到,调用join的父进程,在所有的子进程结束之后才结束的。
3、接着对tree-nojoin.c进行测试,输入./nachos –x ../test/tree-nojoin,得到的测试结果:
部分截图如下:
从图中,我们可以很明显的看出,没有调用join的父进程,在开启另外的两个进程之后,就结束了。
五、实现小结
本次实验的难度是相当大的,其中特别是我们新的内存与虚存的映射分配的代码编写,以及进程管理的编写我们要改在nachos的内存分配机制以支持多道程序,并实现exec和join以及exit的系统调用。
六、文件清单
~/nachos/nachos-3.4/code/Makefile.common
~/nachos/nachos-3.4/code/threads/main.cc
~/nachos/nachos-3.4/code/threads/system.h
~/nachos/nachos-3.4/code/threads/system.cc
~/nachos/nachos-3.4/code/userprog/progtest.cc
~/nachos/nachos-3.4/code/userprog/addrspace.h
~/nachos/nachos-3.4/code/userprog/addrspace.cc
~/nachos/nachos-3.4/code/userprog/memorymanager.h
~/nachos/nachos-3.4/code/userprog/memroymanager.cc
~/nachos/nachos-3.4/code/userprog/syscall.h
~/nachos/nachos-3.4/code/userprog/exception.cc
~/nachos/nachos-3.4/code/userprog/processmanager.h
~/nachos/nachos-3.4/code/userprog/processmanager.cc
~/nachos/nachos-3.4/code/test/tree-nojoin.c
~/nachos/nachos-3.4/code/test/tree.c
~/nachos/nachos04.doc
其中,nachos04.doc为实验报告。