毕设(3):xOS 雏形
xOS
上节仿真系统顺利从 uart 截获数据并顺利打印出字符,这节依赖于此,我要设计 xOS。
目前的 xOS 只支持栈区,也就是说它不支持堆区内存的分配。
启动代码
启动函数做 4 件事情:
- 设置栈指针
- 清零 BSS 段
- 跳到 main
- 从 main 返回(实际上没必要返回,因为 main 返回之前可以设置一个死循环)
因此,启动代码大致如下:
1 | |
这段启动代码很好理解。
main
main 函数是 xOS 的核心,它会初始化有一些配置,比如 uart、中断注册等,之后就会进入到 shell。大致如下:
1 | |
系统初始化
uart
首先是 uart 的初始化,因为我们要给 uart 输送各种调试信息,因此它要最先初始化。上一节已经讨论过,这节不在赘述。
trap 初始化
首先这是一段 myCPU 的前端的代码:
1 | |
由于xOS 不支持虚拟存储系统,因为 tlb 异常永远不会触发。这样看来,不管是异常还是中断,走的都是 csr_eentry。这里可以看出不管是中断还是异常,其实都是 trap。
trap_entry 的设计
当程序在执行的时候,突然来了一个中断或者异常,会怎么办呢?思路就是,保存被中断打断的程序的所有的相关的状态。当trap 的时候,硬件会自动将被打断的指令的 pc 保存到 csr_era 中,并且同时会关闭中断:
1 | |
核心问题是,处理器会跳转到 trap_entry。此时所有通用寄存器都保持着异常发生前的值,我们需要保存这些寄存器到栈上,但是我们需要临时寄存器来完成保存操作本身。这就产生了一个”鸡生蛋”的问题:
- 要保存寄存器,需要用到临时寄存器(比如 t0)
- 但使用 t0 会破坏它原来的值
- 我们又需要保存 t0 的原始值
解决方案就是使用 CSR 作为临时存储:
1 | |
现在 t0 和 sp 可以自由使用了,因为它们的原始值已经安全保存在 CSR 中。
既然 sp、t0 已经保存,接下来就可以使用它们来完成保存上下文的工作了。现在产生了一个新的问题要保存哪些寄存器?
- CSR_ERA /* 异常返回地址 - 必须保存,防止处理中断的时候再次修改 */
- CSR_PRMD /* 异常前的特权级和中断使能状态 - 必须保存,因为处理中断的时候是关中断的,要修改它,处理完得恢复 */
- CSR_ESTAT /* 异常状态(异常类型、中断pending)- 建议保存 */
其它 csr 可以不用管,因为这是裸机系统,接下来建议保存所有的通用寄存器:
1 | |
接着就可以跳转到 trap_dispatch 中进行处理了:
1 | |
trap_dispatch()
a0 作为 trap_dispatch 的第一个参数,在 trap_dispatch 中进行处理的时候,利用 C 语言强大的指针转义直接可以访问刚刚保存的 trap_frame:
1 | |
其中保存的顺序要和 trap_frame 顺序严格一直,不然会给访问徒增难度:
1 | |
ecode 在硬件中是这样定义的:
1 | |
所以我们通过移位+掩码运算就能解析出 ecode。在 loongarch32r 中,如果是中断 ecode 就是 0,异常的话各不相同:
1 | |
因此只需要根据 ecode 就能区分中断和各个异常。假设是一个中断后,进入中断处理函数。这里先说一下中断处理相关的几个寄存器。
csr_estat
ESTAT(Exception Status)寄存器包含:
- IS 字段(Interrupt Status):哪些中断正在 pending(待处理)
- Ecode 字段:异常类型编码
- EsubCode 字段:异常子类型
这里主要关心 IS 字段,它是一个位图,每一位代表一个中断源:
- bit 0: 软件中断 0
- bit 1: 软件中断 1
- bit 2-9: 硬件中断 0-7
- bit 10: 定时器中断
- bit 11: 性能计数器中断
- bit 12: IPI 中断
- …
csr_ecfg
LIE 字段(Local Interrupt Enable):本地中断使能位图。局部中断使能位,高有效。这些局部中断使能位与 CSR.ESTAT 中 IS[12:0]域记录的 13 个中断源一一对应,每一位控制一个中断源。
中断管理
这是本系统设计的 IRQ 标号:
1 | |
可以看到,IRQ_PS2 是 4 号中断。在注册的时候:
1 | |
会把 handler 放到对应的 irq_handlers[],这样 interrupt_handler 就会执行到:
1 | |
假设这里是一个 ps2_irq_handler,
1 | |
这个 handler 会在 bsp_ps2_data_available() 的时候,将 ps2 的数据全部读取到 kb_buffer。然后就返回并恢复现场:
1 | |
然后就回到被中断的指令处:
1 | |
这样,就完成了被中断、中断处理、中断返回并继续执行被打断的指令的过程。
执行效果
可以看到,xOS 工作正常,可以通过 ps2 进行输入,并且中断处理、中断退出等都能正常工作:
1 | |