【nachos】nachos学习笔记(六) 内存扩展和系统调用的实现

实验7

一、分析

  现在的Nachos并没有实现内存页的动态分配,每当其读取一个用户进程时,将其数据按顺序从内存页的起始页开始加载,这导致逻辑页与物理页变成一一对应的关系,因此需要改写创建新AddrSpace时的代码。因为需要对已分配的页进行管理,可以维护一个文件系统中使用过的BitMap对象。

 

二、修改

  【nachos】nachos学习笔记(六) 内存扩展和系统调用的实现

首先需要为AddrSpace类增加一个BitMap类的对象,设为static以让所有类对象共享。

【nachos】nachos学习笔记(六) 内存扩展和系统调用的实现

 Static类型的对象指针要在方法外部初始化。

【nachos】nachos学习笔记(六) 内存扩展和系统调用的实现

在初始化页表时,将一个逻辑页映射到freeMap中空闲的页上。

【nachos】nachos学习笔记(六) 内存扩展和系统调用的实现

同时,析构函数中需要释放freeMap中页。

【nachos】nachos学习笔记(六) 内存扩展和系统调用的实现

初始化分配的页表时,因为页表的分配可能不是连续的(目前来说是连续分配的,因为并没有实现Yelid或sleep等系统调用),因此需要对每一个页表进行置0操作。因为页表的分配方式发生了改变,因此,读取程序代码段和数据段的代码也要进行改变。

【nachos】nachos学习笔记(六) 内存扩展和系统调用的实现

这是对noff文件中代码段的读取。因为页的分配可能是不连续的,因此需要一页一页地进行读取,cursor记录了当前读取的文件游标位置。同时,因为代码段结尾可能不在页的起始位置,因此需要计算结尾的偏移,而代码段的开头为0,但为了和后续对数据段的读取代码统一,还是计算开头的偏移,尽管其在此处为0。开头和结尾所在的页需要进行单独处理,而中间的页总是占满页表,可以循环读取。另外,在读取开头段时要处理开头和结尾都在同一个页中的特殊情况。每读取一页,下一页的物理页数需要通过页表获取。

对数据段的读取与代码段类似,不再赘述。

【nachos】nachos学习笔记(六) 内存扩展和系统调用的实现

此时已完成对内存空间的扩展,但是重新运行程序并看不出变化,也无法进行测试,需要进行实验8

 

实验八

一、分析

  实验八中,需要运行如下用户程序

【nachos】nachos学习笔记(六) 内存扩展和系统调用的实现

该程序通过系统调用Exec来创建一个子进程执行halt这个用户程序,然后返回这个程序的pid。目前的Nachos对除了Halt的系统调用都没有具体实现。实验八要求我们对其中的Exec和Exit进行实现。

二、Exec的实现

  在exec.c的汇编文件exec.s中,程序调用了Exec函数,该函数在syscall,h中声明,在start.s中实现。同时可以看到,exec将调用Exec的函数的参数”../test/halt.noff”的地址放到了$4号寄存器中,该寄存器时用于存放子函数的参数的。

【nachos】nachos学习笔记(六) 内存扩展和系统调用的实现

打开start.s

【nachos】nachos学习笔记(六) 内存扩展和系统调用的实现

可以看出,其中对所有系统调用函数的实现都是通过将系统调用的标志放进$2号寄存器中,然后调用syscall指令,该指令会调用machine类的RaiseException

【nachos】nachos学习笔记(六) 内存扩展和系统调用的实现

而RaiseException会调用exception类的ExceptionHandler来处理中断。

【nachos】nachos学习笔记(六) 内存扩展和系统调用的实现

中断处理程序中,首先将寄存器$2中存储的系统调用类型取出来,然后根据系统调用的类型进行对应的处理。至此我发现了对Exce实现所要修改的程序位置

【nachos】nachos学习笔记(六) 内存扩展和系统调用的实现

接下来的问题为如何获取要执行的文件的路径。上面分析到Exec的参数地址被存放$4号寄存器中,因此需要把4号寄存器的内容取出来,然后逐个往下读取,直到遇到字符串中止符’\0’

【nachos】nachos学习笔记(六) 内存扩展和系统调用的实现

 

  还有一个问题时是如何对Exec创建的用户进程的地址空间编号,以便在进行JOIN系统调用时可以根据这个编号找到对应的进程(虽然没要求实现JOIN)。为使编号唯一,在system中添加一个int数组来记录分配的ID号,并初始化为0

【nachos】nachos学习笔记(六) 内存扩展和系统调用的实现

【nachos】nachos学习笔记(六) 内存扩展和系统调用的实现

【nachos】nachos学习笔记(六) 内存扩展和系统调用的实现

同时为类AddrSpace新增一个spaceID成员,并在构造函数中初始化它。

【nachos】nachos学习笔记(六) 内存扩展和系统调用的实现

创建一个新的用户线程需要创建一个Thread,然后在这个新Thread中执行对新的用户程序的解释执行。Thread的Fork函数的参数指定了这个Thread要执行的函数,这个函数的参数为int,为了新创建的Thread能执行StartProcess来启动对用户程序的执行,可以将StartProcess函数的参数类型改为int,然后再函数内部将int转换成char *,但是这样做需要改变同时这个函数在其他地方的extern声明,因此我复制了一个StartProcess函数,然后把他的参数改成int。

【nachos】nachos学习笔记(六) 内存扩展和系统调用的实现

同时为了在ExceptionHandler能够引用这个函数,需要在exception类前进行声明

【nachos】nachos学习笔记(六) 内存扩展和系统调用的实现

之后我们便可以创建新的Thread,然后执行

【nachos】nachos学习笔记(六) 内存扩展和系统调用的实现

调用Fork后,需要记录创建的线程的地址空间的ID,寻找spaceIDs中第一个未分配的ID号,该ID即为接下来线程将使用的ID,接下来,主线程需要Yield来切换到子线程,当子线程执行完返回后,我们将id写入寄存器2中,以将其赋值给pid。最后,因为处理完系统调用后,原来的指令解释的SWITH中使用了return而非break,因此PC计数器并不会增加,需要调用AdvancePC手动增加。

【nachos】nachos学习笔记(六) 内存扩展和系统调用的实现

至此,Exec的实现已经完成,执行exec.noff进行测试。

【nachos】nachos学习笔记(六) 内存扩展和系统调用的实现

 

三、Exit的实现

  【nachos】nachos学习笔记(六) 内存扩展和系统调用的实现

实验要求还需要将Exit的参数存储起来以便Join获取进程的退出状态,因为无法测试,所以只是简单地将进程结束。最后修改程序进行测试。

【nachos】nachos学习笔记(六) 内存扩展和系统调用的实现

【nachos】nachos学习笔记(六) 内存扩展和系统调用的实现