PA1 RTFSC
框架代码
由于NEMU-PA是一个很庞大的框架系统,所以要在其基础之上开发需要对框架代码进行熟悉。所以最重要的一步应该是阅读程序的源代码。在课件中,已经给出了相关代码的简要结构说明,按照标题简单理解即可:
| 1 | nemu | 
为了支持不同的ISA形式。框架代码将NEMU分成两部分:ISA的相关实现和ISA无关的框架代码。其中不同的ISA被存放在src/isa目录下,用于提供接口,其余部分框架则是相同的实现。这里我们选择RISCV作为我们的ISA,现在我们就可以对整个框架代码进行分析了。
配置系统和项目构建
系统的主要配置文件存放在主目录下的Kconfig文件中,当我们运行make memuconfig时,会弹出一个可视化的编辑界面,程序会将我们的选择对应的添加到include\generate\autoconf.h中,用于编译时设置。从而实现对框架代码的简易配置。
对于更复杂的过程,涉及到makefile的编写,这里暂时忽略。
准备第一个客户应用
NEMU作为一个模拟的计算机系统,主要的功能就是运行客户程序。我们可以从头观察NEMU的项目框架,来查看,NEMU是怎么进行初始化,并且将客户应用加载到内存中运行的。
首先是进入nemu-main.c中,可以看到形如CONFIG_XX的宏定义字样,我们可以在autoconf.h中找到相关的宏定义,根据部分宏的配置,可能会编译时忽略或是开启部分功能。
NEMU的框架代码主要通过函数进行包装,进入nemu-main.c中,首先执行的是init_moniter(),步进程序可以看到monitor的初始化过程,对于mem和seed都是简单的设置,可以之间看源代码
其中init_isa()的代码比较特殊,也比较关键:
| 1 | static void restart() { | 
程序首先将img(这里是初始程序,加载到主机的起始地址),具体的内容可以在isa\risc32\init.c中查看看,restart()的作用是将CPU复位成初始状态,这里主要是将pc置0,并将riscv的第一个寄存器设置为0,作为零寄存器。
通过查看memory目录我们可以知道,NEMU为客户计算机提供了128MB的物理内存,同时我们将客户程序读入到内存的固定内存位置RESET_VECTOR。
在这里我们需要分清楚主机和客户机的区别,主机就是运行NEMU的物理计算机,客户机就是在NEMU上运行的计算机程序。我们使用guest_to_host()和host_to_guest()进行主机和客户机地址的相互转换。guest_to_host()将我们在客户机的物理地址转换成在NEMU内存中的数组地址,host_to_guest()则将内存中的数组地址转换成客户机的物理地址。
我们可以在include\memory\paddr.h中找到对RESET_VECTOR的定义,由于这里我们没有设置CONFIG_PC_RESET_OFFSET所以内存的加载从pmem[0]开始。
接着程序调用load_image()用于向内存中加载程序,如果没有给出img参数,则NEMU使用内置的初始化程序,我们可以在isa/risc32/init.c中看到。
然后程序调用welcome(),我们编译运行时看到的信息就是来自这里。
运行第一个客户运用
在monitor完成初始化之后,nemu-main.c会进入下一个程序engine_start中的sdb_mainloop(),并输出提示符指示输入:
| 1 | (nemu) | 
在src\monitor\sdb\sdb.c中,程序预设了一个cnd_table,设置在sdb中支持的指令:
| 1 | cmd_table [] = { | 
对于参数的处理和选择执行可以通过阅读sdb_mainloop理解,这里我们主要将注意力放到cmd_c()的调用函数cpu_exec()上,它是我们模拟器运行程序的cpu执行的核心,这里传入了一个参数-1但由于是uint64_t表示,所以实际上的数值是0xFFFFFFFFFFFFFFFF,即持续执行,这里我们进一步的步入追踪,最终查看到exec_once(),他负责将pc设置成下一条指令执行的位置。
现在NEMU会不断的进行执行,首先它执行的便是我们的内置程序:
| 1 | static const uint32_t img [] = { | 
在NEMU中我们将ebreak的语义设置成,接受a0的数据作为退出状态。同时为了检测客户程序的退出,设置了以下三种状态:
- HIT GOOD TRAP- 客户程序正确地结束执行
- HIT BAD TRAP- 客户程序错误地结束执行
- ABORT- 客户程序意外终止, 并未结束执行
我们在nemu中使用c就可以获得以下输出:
| 1 | nemu: HIT GOOD TRAP at pc = 0x8000000c | 
即nemu的客户程序在pc = 0x8000000c处成功退出。退出cpu_exec()之后,我们再使用q退出nemu程序。
优美的退出
我们运行NEMU后直接使用q会产生报错:
| 1 | ylin@Ylin:~/ics2025/nemu$ make run | 
我们需要找出原因并解决这个问题。
这是cmd_q的源代码:
| 1 | static int cmd_q(char *args) { | 
我们输入q后会因为sdb_mainloop的判断逻辑退出到nemu_main执行is_exit_status_bad():
| 1 | int is_exit_status_bad() { | 
程序会检测nemu的状态而决定以什么情况退出,我们之前的报错则是因为,我们没有为NEMU设置任何状态,NEMU以默认状态退出,因此返回错误。想要优雅的退出,我们只需要再退出前设置好NEMU的状态。因此我们对cmd_q()函数进行重写
| 1 | static int cmd_q(char *args) { |