PA3.1

1.实现loader加载器功能

    框架提供有对磁盘操作的函数接口(详细用法见讲义和代码),需注意的是使用时要先进行声明(我是声明在common.h文件中)。要实现的功能也很简单,从磁盘读数据,然后将数据写入内存。一行代码即可完成。
    开始我以为和之前一样0x4000000是模拟内存的地址(之前的pemu数组),结果翻遍了好几个文件都没有找到如何转化模拟内存地址和物理内存地址的宏或其他方法,最后仔细看了看读写磁盘的函数,发现这里的地址都是无需处理成实际物理内存地址的,所以写内存的函数参数直接使用给出的起始地址宏即可。

    若实现成功,运行后(注意现在是在nanos-lite目录下make run)会提示0xcd即pusha指令未实现。
这里写图片描述

2.实现中断调用

    首先简单说明一下原理。在中断跳转时,需要根据中断号(此处即0x80)在IDT数组中求得需要跳转的位置,并设置跳转。因此首先需要加载IDT数组信息(LIDT指令),然后处理信息,获取跳转目标地址(INT指令)。

    1)实现CS和IDTR寄存器
    添加寄存器结构并完成对应的初始化,要完成的工作和之前的eflag一样。其中CS就是一个16位寄存器,而IDTR共有48位,其中16位是limit部分,表示IDT数组长度,32位是base部分,表示IDT数组的起始地址。

    2)实现LIDT指令
    根据讲义描述,提到set_idt函数会对IDTR寄存器进行初始化。可以看到,传入函数的参数为idt的首地址和长度。
这里写图片描述
    继续进一步查看代码,可以看到,IDT数组的相关信息都存入了data数组中,然后使用内联汇编将data地址传入作为了lidt指令的操作数,由此联想到在lidt指令执行函数中应当是访问该地址上的连续3个字节并赋值给IDTR寄存器对应的部分。
这里写图片描述
    实现指令还是老规矩,执行函数两行代码解决。注意这里是在nemu层次中,访问的内存是虚拟内存,因此要使用函数访问,而前面实现加载器是在nano-lite层次,内存按实际地址访问(具体不同层次的内存经过一个函数的调用就不一样的原因我也不清楚,反正这次前两步都因为这个坑卡了好久,还被金航学长水了一顿)。

    另外手册可能描述不是很清楚,可以参考下图:
这里写图片描述
    4)实现int指令
    还是老规矩,注意将代码写在raise_intr函数中方便以后复用即可。具体过程就是寄存器压栈,根据索引取出IDT数组信息(注意看注释),检测P位是否为1,若为1则更新CS,处理结构体信息得到跳转目标地址并设置跳转。

    正确实现后,运行后将会提示0x60即pusha指令未实现。

3.保存现场

    类似于函数调用前要将参数压栈,这里系统调用前要保存各个寄存器的状态,于是就要用到push(push all)指令,并模拟trapframe的实现。

    1)实现pusha指令
    还是老规矩。。。。。。

    2)正确实现regset结构体
    这个其实就是根据各个内容压栈的顺序调整结构体中各个成员的顺序。讲义说通过理解trapframe的形成过程来实现(实际上trapframe的结构远比讲义里的复杂,讲义里的结构很简单)。emmmm……“讲义是不可能认真看的,这辈子都不可能认真看讲义的,别人talk写得又好,博客写得又骚,我超喜欢看别人写的东西做PA的”,考虑到很多人的这种心态,我还是提示一下。
    下面划关键句子:
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
    再不懂的看下图:
这里写图片描述
    这还不懂的话我只能说这是我在违法的边缘试探的极限了。
    最终正确实现的话运行结果应当是触发8号事件。pusha的实现应当是没有其他问题了,如果有,那都是PA2的坑了,这里不讨论。如果是触发3号事件或者是其他的问题,然后又觉得自己的结构体成员顺序没有问题的话,把顺序颠倒过来试一试,说不定有惊喜(别问我怎么知道的)。

4.系统调用处理并恢复现场

    1)系统调用处理
    在switch-case结构中添加相应的分支即可,这里是有两层处理——首先是识别出是系统调用事件(_EVENT_SYSCALL),因此调用do_syscall()函数;然后进一步识别是哪一种系统调用,这里要实现的有两种(SYS_none和SYS_exit),再分别调用函数sys_none(自己编写,要求见讲义)和_halt(函数原型void _halt(int code),在nexus-am目录下的SPEC.md文件中有说明当code为0时表示正常终止)。
    至于获得正确系统调用参数和设置系统调用返回值,需要用到SYSCALL_ARGx(r)系列的宏。首先根据字面意识,就是系统调用(system_call)参数(arguments),然后RTFSC可以看到r是前面实现的结构体类型的指针。这里借用一下talk中的内容:
这里写图片描述
这里写图片描述
    想想SYSCALL_ARGx(r)系列的宏有几个,想想r中有哪些成员。这样一看简直EASY。

    2)恢复现场
    首先是和pusha对应的popa,依旧老规矩,一般操作。
    然后是iret指令。在trap.s文件中可以看到,在执行iret指令前,有一句add $0x8, %esp,相当于有两个字节出栈(想想陷阱帧的结构,通用寄存器和int指令压栈的内容之间是什么),也就是说执行iret指令时栈顶已经是eip了,剩下的就是出栈并设置跳转返回发生系统调用的地方了。

    成功实现了除SYS_exit以外的其他部分后,会提示触发4号事件;完整完成整个PA3.1后,运行dummy将会HIT GOOD TRAP。


猜你喜欢

转载自blog.csdn.net/qq_21110935/article/details/80292718
3.1