以下代码,是riscv plic处理一个外部中断的handler程序。在handler程序中
-
首先写irq_generator组件的clear寄存器,将中断源给清除掉
-
然后写plic的complete寄存器,通知plic,该中断处理完毕
plic_irq_mmode: li a1, PLIC_M_CLAIM // a0 save the interrupt id // tell the irq_generator to clear the interrupt li a2, 0x10040300 sw a0, (a2) lw a3, (a2) // write the complete sw a0, (a1) ret |
上述的中断处理程序,理论上可以完美工作。
但是在实际的仿真过程中,发现,当该中断处理完毕,异常返回后,该中断被再次的触发,然后又再次进入中断处理程序。这就与预期的不符合,因为发生了一次中断,但是cpu却处理了两次。
通过抓取仿真的波形,找到了该中断,再次被cpu响应的原因。
下图是波形:
从波形可以看出,写plic的complete寄存器的时间,要早于写irq_generator组件的clear寄存器的时间。因此造成了,PLIC收到core的中断处理完毕请求后,认为该中断处理完毕,因此可以再次接收该中断的外部中断请求,而此时,外部中断请求还没有clear掉,所以PLIC认为又有一个新的中断请求到来,从而记录该中断,然后向cpu上报该中断。
究其原因,是cpu的硬件,将我们写的程序流的仿存顺序,给乱序仿存了。从而造成了程序非预期的结果。因为cpu是流水线工作,而且为了性能上的考虑,会将顺序仿存,变成乱序仿存,从而提高性能。
所以当我们要求,执行的仿存顺序,是强保序的情况下,就需要额外插入fence指令。
在riscv的spec,定义了fence指令的格式。
该fence指令,比较复杂,之后,专门写篇文章介绍这个。
现在把中断处理程序改为:
plic_irq_mmode: li a1, PLIC_M_CLAIM // a0 save the interrupt id // tell the irq_generator to clear the interrupt li a2, 0x10040300 sw a0, (a2) lw a3, (a2) fence o, o // write the complete sw a0, (a1) ret |
然后重新仿真,此时看波形,就正确了。
访问PLIC外设,在时间上,是晚于访问irq_generator外设的,正和我们程序预期的是一致的。
cpu在访问外设时,是不一定会保证不同外设之间的访问,是保序的。只是会保证同一个外设的访问,是保序的。
也就是,如果是访问不同的外设,比如如下程序流:
访问外设A 访问外设B 访问外设C |
在硬件里面的真实访问顺序,可能为:
访问外设B 访问外设C 访问外设A |
因此如果程序想要硬件的访问顺序和程序流一致,就需要显示的增加fence指令。
但是对于访问相同的外设,比如如下程序流:
访问外设A寄存器a 访问外设A寄存器b 访问外设A寄存器c |
在硬件里面的真实访问顺序,是保证和程序流的顺序是一致的。
大佬,请问一般的riscv-32的程序镜像内存布局是什么样子,程序执行的代码、堆,栈,全局静态数据等放在4G内存空间中的什么位置