实验目的
尝试实现系统调用Exec() 和 Exit()
实验步骤
需要注意,在前三个步骤不需要修改代码。
一、nachos中系统调用的实现机制
观察nachos/machine/machine,mipssim中的实现可以看出,每一条用户程序中的指令在虚拟机中被读取后,包装成一个OneInstruction对象,经过解码,在mipssim中的一串长SWITCH语句中分别执行不同的操作。
而对于系统调用,在用户程序中的表现是调用nachos/userprog/syscall.h中定义的一系列系统调用函数,而这些函数的实现是定义在nachos/test/start.s中的汇编代码。
可以看出,实际上start.s中,不同的系统调用实际上是把系统调用的类型(SC_***)放入二号寄存器,然后执行指令syscall。
在mipssim中的switch语句中,对syscall的处理如下:
可以看出,这里抛出了一个异常,交给异常处理函数去处理。要注意的是,这里不同于其他指令的处理方式,没有使用break而是使用了return,而一般指令在break之后会进行一些其他处理:
可以看出其中包括了对pc的推进操作。
所以,直接return造成的后果是没有对pc进行推进,那么程序就会再次读入这条系统调用操作,无限循环。所以我们要手动添加这部分对pc推进的代码,稍后会讲到。
而RaiseException函数实际上最终调用的是nachos/userprog/exception.cc中的函数ExceptionHandler(ExceptionType which)。在这里ExceptionType是int的别名,可以当做int处理。
在这个函数中,我们可以看到,它利用了条件判断语句来进行判断,当前错误是系统调用还是用户程序出错,并进一步判断是什么错误。
也不难看出,程序当前只处理了Halt这个系统调用。
所以要完成Exec和Exit,就要在这里添加实现。
二、对Exec和Exit系统调用的分析
对于Exec系统调用来说,在syscall.h中的定义如下:
该调用的功能是,从可执行文件name运行一个新的用户程序,并行执行,并返回新的程序的内存空间标识符SpaceId。
具体的实现思路:根据name指向的地址打开可执行文件,创建AddrSpace,再fork出一个新的线程,将其内存空间设为刚创建的AddrSpace对象,目标函数中,对AddrSpace对象调用InitRegister和RestoreState方法,设置好虚拟机寄存器、内存空间的状态,调用machine->Run()执行。而执行系统调用的线程在fork出新线程后,将SpaceID返回,然后调用AdvancePC使pc寄存器的值前进,避免循环。
对于Exit系统调用来说,在syscall.h中的定义如下:
该调用的功能是,结束用户程序的运行。传入0时正常退出。由于我们没有其他退出情况要处理,所以这个状态码暂时不利用。
具体实现思路:简单地调用currentThread->Finsh()终止当前线程即可。
三、系统调用中对参数、返回值的读取和传递
系统调用中的返回值不同于普通函数的参数传递,参照exception.cc中的注释可以知道,它们是通过寄存器来储存的。
可以看出,参数分别储存在r4到r7内。
而返回值需要放在r2寄存器中。
而我们的参数不能以直接转换成指针的形式从内存中读取,原因是这里的内存地址实际上是虚拟机的内存,而转换成指针形式的内存地址是真实机器中的地址。所以读取要用machine->ReadMem(int addr,int size,int *value)方法。而读取字符串,则需要循环至读取到\0为止。