nju pa3

穿越时空的旅行 异常响应机制及CTE 实现异常响应机制 我们要实现操作系统的自陷功能,虽然中断的大致原理和流程上课都讲过,但不同操作系统有着不同的具体设计,因此在这之前有必要结合文档与源码过一遍我们框架代码中“异常响应机制”的流程 riscv对于中断与异常提供了各种令人眼花缭乱的CSR控制状态寄存器,我们这里涉及到的有: mtvec寄存器 - 存放了发生异常时处理器需要跳转到的地址 mepc寄存器 - 存放发生异常的指令地址,用与异常处理返回时能回到原本程序执行的位置 mstatus寄存器 - 存放处理器的状态 mcause寄存器 - 存放异常的种类 另外根据文档中提到 首先当然是对R的扩充, 除了PC和通用寄存器之外, 还需要添加上文提到的一些特殊寄存器 所以我们先给处理器添加上述的几个CSR typedef struct { word_t mcause; vaddr_t mepc; word_t mstatus; word_t mtvec; } riscv64_CSRs; typedef struct { word_t gpr[32]; vaddr_t pc; riscv64_CSRs csr; } riscv64_CPU_state; 定义好了需要到的寄存器后,接下来我们就结合nanos-lite的运行来分析中断响应流程吧~在此之前需要再次明确一下模拟器的各层的关系:am是硬件层(确切地说是抽象硬件,对操作系统屏蔽了架构的差异),nanos-lite是操作系统层。我们从nanos-lite的入口,main函数看起,其中中断相关我们只需要关注init_irq以及yield,先回忆一下,还记得文档说的那句话吗? 我们刚才提到了程序的状态, 在操作系统中有一个等价的术语, 叫"上下文". 因此, 硬件提供的上述在操作系统和用户程序之间切换执行流的功能, 在操作系统看来, 都可以划入上下文管理的一部分. init_irq里调用的cte_init,其实就是操作系统向硬件注册事件发生(如中断)的回调函数do_event,这个回调函数就是真正把异常交给操作系统处理的地方(要与异常处理入口函数区分开来),其中的参数为事件和相关的程序上下文。那么这个回调函数什么时候被调用呢,显然是异常发生的时候。 我们接着查看cte_init的代码,其功能简单地说就是保存异常处理入口函数地址,以及保存用户回调函数即上述的do_event,以便异常处理过程中时调用这个用户回调函数。其中异常处理入口函数是__am_asm_trap,这个函数在trap.S这个文件中用汇编语言实现,暂时不管,先接着看流程。 接下来在main函数的最后调用了yield,如果说init_irq描述如何处理异常,那么yield就是真正触发了一个异常(自陷),并进入之前注册的异常处理函数进行异常处理。yield只有两句汇编指令 li a7, -1 ecall 将异常种类存放到a7寄存器中,以及发起自陷,其中ecall会使得程序流程转到之前注册的异常处理入口函数中去执行,即__am_asm_trap,这里就得分析一下这个函数都干了些什么了: __am_asm_trap: ... jal __am_irq_handle ... mret 目前只关注运行流程,多余的代码先去除,__am_asm_trap简单来说是提供了统一的异常入口地址,主要作用是将csr和gpr的内容作为参数调用__am_irq_handle并在其返回后把csr和gpr的新值再存回去(值得一提的是,csr和gpr作为cpu的寄存器,am将他们包装在上下文结构中传给了操作系统,而不是让操作系统直接访问cpu,体现了处处都是抽象和屏蔽的艺术)。__am_irq_handle这个函数也是定义在抽象硬件层(am)中的,通过判断程序上下文内容(比如在riscv-nemu中通过分支mcause的值)来构造事件,最终将事件和上下文一并通过回调函数传给操作系统,开始真正的异常处理….至此从异常注册到异常触发及响应的流程分析就结束了,如果说PA3之前的工作还没对这些抽象硬件、操作系统层等形成认知,或者到了PA3这个部分依然存疑,建议一定要好好做这一小节的内容并去认真体会它是如何设计的,因为确实值得。 若理解了流程,剩下的填代码环节就是顺便的事情了。首先实现几条指令,csr的读写指令和ecall指令 INSTPAT("??????? ????? ????? 001 ????? 11100 11", csrrw , I, R(dest) = CSR(imm); CSR(imm) = src1); INSTPAT("??????? ????? ????? 010 ????? 11100 11", csrrs , I, R(dest) = CSR(imm); CSR(imm) |= src1); INSTPAT("0000000 00000 00000 000 00000 11100 11", ecall , I, ECALL(s->dnpc)); 其中两个新宏CSR, ECALL如下: ...

三月 1, 2023 · by NOSAE

nju pa2

其他资料: https://github.com/riscv-non-isa/riscv-asm-manual/blob/master/riscv-asm.md http://riscvbook.com/chinese/RISC-V-Reader-Chinese-v2p1.pdf RTFM 运行第一个客户程序 第一个客户程序即文档所说的dummy.c,键入命令后,会将dummy.c编译成基于rv64指令的二进制格式文件(后缀名为 .bin),作为nemu模拟器的镜像文件(img_file) make ARCH=$ISA-nemu ALL=dummy run 实现指令 首先查看反汇编结果,看看需要实现哪些指令 0000000080000000 <_start>: 80000000: 00000413 li s0,0 80000004: 00009117 auipc sp,0x9 80000008: ffc10113 addi sp,sp,-4 # 80009000 <_end> 8000000c: 00c000ef jal ra,80000018 <_trm_init> 0000000080000010 <main>: 80000010: 00000513 li a0,0 80000014: 00008067 ret 0000000080000018 <_trm_init>: 80000018: ff010113 addi sp,sp,-16 8000001c: 00000517 auipc a0,0x0 80000020: 01c50513 addi a0,a0,28 # 80000038 <_etext> 80000024: 00113423 sd ra,8(sp) 80000028: fe9ff0ef jal ra,80000010 <main> 8000002c: 00050513 mv a0,a0 80000030: 00100073 ebreak 80000034: 0000006f j 80000034 <_trm_init+0x1c> 通过查询手册,可以知道li ret等指令都是伪指令,比如第一条指令 80000000: 00000413 li s0,0 其十六进制内容为0x00000413,对应二进制为0000 0000 0000 0000 0000 0100 0001 0011,查询手册可知其为addi指令,而当伪指令li中的立即数小于4096时,的确会被编译器展开为addi指令,因此目前可以确定,反汇编结果的右边的指令只是为了方便阅读(生成反汇编指令的代码在disasm.cc中),我们在实现指令功能时,只需要看左边的十六进制内容即可 按照框架和文档提示,以及根据手册查询每条指令干了什么,然后填写代码即可 enum { TYPE_I, TYPE_U, TYPE_S, TYPE_J, TYPE_N, // none }; #define immJ() do { *imm = SEXT(( \ (BITS(i, 31, 31) << 19) | \ BITS(i, 30, 21) | \ (BITS(i, 20, 20) << 10) | \ (BITS(i, 19, 12) << 11) \ ) << 1, 21); Log(ANSI_FG_CYAN "%#lx\n" ANSI_NONE, *imm); } while(0) static void decode_operand(Decode *s, int *dest, word_t *src1, word_t *src2, word_t *imm, int type) { // ... switch (type) { // ... case TYPE_J: immJ(); break; } } static int decode_exec(Decode *s) { // ... INSTPAT("??????? ????? ????? 000 ????? 00100 11", addi , I, R(dest) = src1 + imm); INSTPAT("??????? ????? ????? ??? ????? 11011 11", jal , J, s->dnpc = s->pc; s->dnpc += imm; R(dest) = s->pc + 4); INSTPAT("??????? ????? ????? 000 ????? 11001 11", jalr , I, s->dnpc = (src1 + imm) & ~(word_t)1; R(dest) = s->pc + 4); // ... return 0; } 程序,运行时环境与AM 实现字符串处理函数 根据前面的铺垫,这部分比较简单,就不贴代码了,只需要: ...

一月 24, 2023 · by NOSAE

nju pa1

note: 基于riscv64,不适配riscv32 RTFSC 优美地退出 make run启动nemu后直接输入q退出,得到如下最后一行的错误 Welcome to riscv32-NEMU! For help, type "help" (nemu) log Unknown command 'log' (nemu) q make: *** [/home/ubuntu/ics2022/nemu/scripts/native.mk:38: run] Error 1 是由于is_exit_status_bad函数返回了-1,main函数直接返回了此函数返回的结果,make检测到该可执行文件返回了-1,因此报错。通过分析该函数得到解决方案:在输入q中途退出nemu后,将nemu_state.state设成NEMU_QUIT即可 // nemu/src/monitor/sdb/sdb.c void sdb_mainloop() { ... int i; for (i = 0; i < NR_CMD; i ++) { if (strcmp(cmd, cmd_table[i].name) == 0) { if (cmd_table[i].handler(args) < 0) { if (strcmp(cmd, "q") == 0) { nemu_state.state = NEMU_QUIT; // set "QUIT" state when q } return; } break; } } if (i == NR_CMD) { printf("Unknown command '%s'\n", cmd); } } } 此时再通过make run运行后中途键入q命令退出模拟器将不会再报该错误 简易调试器 单步执行 si [N] 第一步,在cmd_table注册一条命令si static struct { const char *name; const char *description; int (*handler) (char *); } cmd_table [] = { { "help", "Display information about all supported commands", cmd_help }, { "c", "Continue the execution of the program", cmd_c }, { "q", "Exit NEMU", cmd_q }, { "si", "Continue the execution in N steps, default 1", cmd_si }, /* TODO: Add more commands */ }; 第二步,编写cmd_si,即si具体要执行的东西 ...

一月 12, 2023 · by NOSAE