南大-ICS2021 PA3.1 学习笔记&记录
为了实现最简单的操作系统, 硬件还需要提供一种可以限制入口的执行流切换方式. 这种方式就是自陷指令, 程序执行自陷指令之后, 就会陷入到操作系统预先设置好的跳转目标. 这个跳转目标也称为。操作系统运行在较高的等级, 有权限访问所有的代码和数据, 一般的用户程序运行在较低的等级, 只能访问自己等级下的代码和数据. 模式的检查通常使用门电路来实现.进程(正在运行的程序), 例:如果你打开了3次记事本,
PA3.1
代码github网址:
https://github.com/xiao-tai/ics2021
ICS2021其他博客
南大-ICS2021 PA1~PA2.2 学习笔记&记录
南大-ICS2021 PA2.3 学习笔记&记录
南大ICS2021–实现库函数vsnprintf
最简单的操作系统
操作系统的一个主要任务就是"自动加载新程序", 即要在程序之间的执行流进行切换
-
Nanos-lite, 是南京大学操作系统Nanos的剪切版, 在AM看来, 它只是一个调用AM API的普通C程序而已.
-
进程(正在运行的程序), 例:如果你打开了3次记事本, 计算机就会有3个记事本进程在运行, 但磁盘上的记事本程序只有一个.
一开始, 在nanos-lite/include/common.h
中所有与实验进度相关的宏都没有定义, 此时Nanos-lite的功能十分简单. 我们来简单梳理一下Nanos-lite目前的行为:
-
打印Project-N的logo.
-
调用
init_device()
对设备进行一些初始化操作. 目前init_device()
会直接调用ioe_init()
. -
初始化ramdisk. 定义俩个
uint_8
变量ramdisk_start和ramdisk_end, '磁盘’的大小就为俩个变量地址的差. -
init_fs()
和init_proc()
, 分别用于初始化文件系统和创建进程, 目前它们均未进行有意义的操作, 可以忽略它们. -
调用
panic()
结束Nanos-lite的运行.
操作系统主要实现两点功能:
-
用户程序执行结束之后, 可以跳转到操作系统的代码继续执行.
-
操作系统可以加载一个新的用户程序来执行.
新需求: 需要进行程序流的切换, 还要当一个用户程序出错了, 操作系统可以运行下一个用户程序, 而不是整个系统崩溃.
等级制度
操作系统运行在较高的等级, 有权限访问所有的代码和数据, 一般的用户程序运行在较低的等级, 只能访问自己等级下的代码和数据. 模式的检查通常使用门电路来实现.
自陷指令
为了实现最简单的操作系统, 硬件还需要提供一种可以限制入口的执行流切换方式. 这种方式就是自陷指令, 程序执行自陷指令之后, 就会陷入到操作系统预先设置好的跳转目标. 这个跳转目标也称为异常入口地址.
穿越时空的旅程
riscv32提供了一些特殊的系统寄存器, 叫控制状态寄存器(CSR寄存器). 在PA中, 我们只使用如下3个CSR寄存器:
-
mtvec寄存器 - 存放异常的入口地址(ecall指令所使用)
-
mepc寄存器 - 存放触发异常的PC
-
mstatus寄存器 - 存放处理器的状态
-
mcause寄存器 - 存放触发异常的原因
riscv32触发异常后硬件的响应过程如下:
SR[mepc] <- PC
SR[mcause] <- 一个描述失败原因的号码
PC <- SR[mtvec]`
实现异常响应机制
问: AM究竟给程序提供了多大的栈空间呢?
答: 对于地址空间的大小, 都定义在
am/src/platform/nemu/include/nemu.h
中, 可能栈空间大小就是宏定义PMEM_SIZE
?
首先, 在nemu/src/isa/riscv32/include/isa-def.h
中添加上述提到的寄存器.
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;
CTE定义了如下数据结构:
typedef struct
{
enum
{
EVENT_NULL = 0,
EVENT_YIELD, EVENT_SYSCALL, EVENT_PAGEFAULT, EVENT_ERROR,
EVENT_IRQ_TIMER, EVENT_IRQ_IODEV,
} event;
uintptr_t cause, ref;
const char *msg;
} Event;
nanos-lite中调用init_irq()
, 接着调用am/src/riscv/nemu/cte.c
中的cte_init
来设置异常入口地址, 执行了csrw
指令, 因为am/src/riscv/nemu/cte.c
中的yield
函数会调用ecall
命令, 所以也需要再实现一下.
def_INSTR_IDTAB("??????? ????? ????? ??? ????? 11100 11", I , csr);
def_THelper(csr) {
def_INSTR_TAB("??????? ????? ????? 001 ????? ????? ??", csrrw);
def_INSTR_TAB("??????? ????? ????? 010 ????? ????? ??", csrrs); //识别csr的读写操作
def_INSTR_TAB("0000000 00000 00000 000 00000 ????? ??", ecall);
return EXEC_ID_inv;
}
接下来实现三个新指令, 新建一个csr.h
, 注意要在nemu/src/isa/riscv32/include/isa-exec.h
添加上新建的头文件.
static vaddr_t *csr_id_instr2address(word_t imm) {
switch (imm)
{
case 0x341: return &(cpu.csr.mepc);
case 0x342: return &(cpu.csr.mcause);
case 0x300: return &(cpu.csr.mstatus);
case 0x305: return &(cpu.csr.mtvec);
default: panic("Unknown csr");
}
}
#define csr(imm) csr_id_instr2address(imm)
def_EHelper(csrrw) {
vaddr_t *csr = csr(id_src2->imm);
vaddr_t temp = *csr;
*csr = *dsrc1;
*ddest = temp;
}
def_EHelper(csrrs) {
vaddr_t *csr = csr(id_src2->imm);
vaddr_t temp = *csr;
*csr = temp | *dsrc1;
*ddest = temp;
}
def_EHelper(ecall) {
bool success = false;
word_t trap_no = isa_reg_str2val("$a7", &success);
if(!success)
Assert(0, "Invalid gpr register");
word_t trap_vec = isa_raise_intr(trap_no, s->snpc);
rtl_j(s, trap_vec);
}
根据触发异常后的响应过程实现isa_raise_intr()
word_t isa_raise_intr(word_t NO, vaddr_t epc) {
cpu.csr.mcause = NO;
cpu.csr.mepc = epc;
return cpu.csr.mtvec;
}
后面的实在是太难了, 我这个小菜鸡选择跑路了…
参考博客
更多推荐
所有评论(0)