南京大学计算机系统基础实验 ICSPA(2023)PA 2 阶段 2
程序, 运行时环境与AM
AM 给我的感觉就是在硬件和软件之间的一个兼容层。有点像 rCore 当中使用的 Rust-SBI,提供了一组用于直接和硬件交互的 API。相比于操作系统,AM 感觉更像驱动,它并且没有操作系统那么多复杂的功能,不会进行进程管理,只是给它上层的软件提供和硬件交互的 API 而已。所以可能操作系统下面也可以有一个 AM?
这样做的好处,当然是把软件和硬件解耦和了。每次有一个新的架构发布的时候,或着新的操作系统发布,都只需要多维护一份代码就可以了。
通过批处理模式运行NEMU
至于第一个必做题,从 main()
开始找,稍微找一点就可以找到,在 sdb_mainloop()
里面一开始就判断了是否是批处理模式。找到这个变量被修改的地方,可以发现是通过传递 -b
参数来开启 NEMU 的批处理模式。于是,只需要在 Makefile 文件中 找到 NEMU 启动时候传入的参数 NEMUFLAGS
,加上 -b
参数就好了。
实现字符串处理函数
这其实没什么好说的,就是基础的语法题。完成这个任务的前提是需要实现大部分的指令。
实现 sprintf
sprintf()
的实现相比上一个任务来说就有趣的多了。这里第一次接触到了可变参数。
可变参数是由 stdarg.h
库提供的。其关键的几个宏和使用方法如下:
|
|
在我们需要实现的代码中可以看到,vsprintf()
的参数里有一个 va_list ap
变量,所以我们在 sprintf()
中只需要使用宏 va_start
来初始化可变参数,然后调用 vsprintf()
将其传入进行处理即可。具体的字符串处理部分是在 svprintf()
里面实现的。
这里也运用了讲义上面讲到的「抽象」的思想,将不同用途的 printf 抽象出来一个 vsprintf()
,然后其他地方只需要将参数进行一些处理,然后调用 vsprint()
就可以了。大大减少了代码的重复率。
需要注意的是,sprintf()
等函数也会在输出最后添加 '\0'
,我因为这个被卡了半小时……
重新认识计算机: 计算机是个抽象层
PA 里反复强调了一个概念:程序是一个状态机。这个概念也在 jyy 老师的另一门课《操作系统:设计与实现》当中反复强调。当有了这个概念之后,再去理解计算机到底是怎么实现执行我们编写的指令,就会豁然开朗:其实计算机就是不停根据当前寄存器、内存的状态,按照指令指引的方向,不停进入到下一个状态。
而对于程序来说,计算机其实给我们提供了跟硬件交流的接口,通过这些接口,我们可以在不需要知道底层硬件实现原理和运行方式的情况下,实现对硬件的操控。
基础设施(2)
实现 iringbuf
环形缓冲区其实就是一个循环数组,循环将每次执行的指令保存下来,满了就直接覆盖就好了。
我新建了 include/cpu/iringbuf.h
文件,在里面实现了 iringbuf 的相关内容,然后在 NEMU 进行记录和退出的地方调用函数进行记录和输出。Kconfig
文件中也需要新建一个新的选项 IRINGBUF
,这样就可以通过判断宏标记 CONFIG_IRINGUF
来动态编译代码。
实现效果:
实现 mtrace
mtrace 的实现就没什么好说的了。按照讲义来写就好了。另外,我觉得在终端中输出 mtrace 没有太大的意义,所以我只将其写入了 log 文件中。
我在实现的时候发现了一个问题,如果按照讲义在 paddr_read()
中实现 mtrace 的话,会发现每次指令指令都会输出。这是因为 NEMU 的取指调用的 vaddr_ifetch()
函数,其实里面调用了 paddr_read()
。
所以我最后是在 vaddr_read()
和 vaddr_write()
中实现的 mtrace。如果后面遇到了问题再改吧。
实现效果如下,与内存访问有关的指令在 log 文件中都输出了 mtrace:
实现 ftrace
有关符号表的内容我正好最近在实习的时候有接触过,所以实现起来还是比较简单的。其实本质上符号表就是一个「名称-地址」映射表,记录着每一个符号对应的地址。而对于函数来说,就是函数名对应其第一行指令的地址。