我正在写一个模拟器,我很难弄清楚CPU应该做什么。我有一个几乎一直可以工作的系统。如果我非常小心地选择CLNT:CPU定时器比例,我就可以运行NOMMU RV32 Linux内核,这真的很幸运。
我正在尽我所能模仿这个堆栈溢出的答案:RISC-V中断处理流
而且,老实说,它似乎运行良好。我可以运行用户空间程序,并让我的计时器以250 Hz运行,除了有时内核崩溃,更具体地说,它几乎总是在__stack_chk_fail
上。经过挖掘,我仍然不知道内核对中断的处理如何不会到处崩溃。因为,它看起来像是在一个中断上,发生了以下情况:
- 如果为零(第一次发生),则将寄存器
tp
交换为CSRCSR_SCRATCH
,并将它们设置为相等 - 从
tp
的描述符中读取sp
、s0
等 - 对中断进行操作
但是,tp
似乎与当前运行的(中断的任务)相同。因此,中断中似乎没有理解对sp
的更改,更具体地说是堆栈保护器的更改,因此它可以覆盖中断任务当前运行的内存。事实上,我已经深入了解并观察到了这一点。
但是我的内核似乎是在QEMU中启动的,所以我认为问题出在我的代码上。
为了参考,这是我的设置:我的定时器订购是:
- 如果计时器>timer_match AND timermatch!=0设置
mip.mtip
,否则清除 - 如果
mie.mtie == 1
ANDmip.mtip == 1
ANDmstatus.mie == 1
,则激发中断
当触发定时器中断时(原子):
- 设置
mstatus.mpie = mstatus.mie
- 设置
mstatus.mie = 0
- 设置
mepc = pc
- 设置
mtval = 0
- 设置
mtcause = 0x80000007
- 设置下一台PC=
mtvec
(Linux使用一体式中断处理程序)
处理陷阱时,I(原子):
- 设置
mepc = mtval = pc
<lt注意:这似乎是正确的,规范是这样说的,内核推进了mepc - 设置
mtval = pc
- 设置
mstatus.mpie = mstatus.mie
- 设置
mstatus.mie = 0
- 设置下一个PC=
mtvec
调用mret
I时(原子):
- 设置
mstatus.mie = mstatus.mpie
- 设置
mstatus.mpie = 1
- 设置下一个PC=
mepc
有什么想法吗?
这可能不是100%正确的,但这似乎能够使系统稳定。
从pi maker的rvc中可以看出,内核正确运行至关重要——必须设置mstatus.mpp
位,否则它将覆盖内存。
正确的解决方案似乎在这里:https://github.com/PiMaker/rvc/blob/master/src/trap.h#L48
但是,为了进行测试,我能够通过以下方式使我的项目发挥作用。除了上述操作之外。。。
如果定时器中断:
- 将最后一个
privilege
存储到mstatus.mpp
中 - 将新的
privilege
设置为3
如果像ebreak
:这样的陷阱
- 将最后一个
privilege
存储到mstatus.mpp
中 - 将新的
privilege
设置为3
如果mret
:
- 将最后一个
privilege
存储到mstatus.mpp
中 - 将新
privilege
设置为旧mstatus.mpp
我不将privilege
位用于任何其他用途。(请注意,这可能是不正确的,因为所选CSR应该根据权限级别而更改,但这似乎是有效的,\(ツ)/