以下代碼,是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 |
在硬件裏面的真實訪問順序,是保證和程序流的順序是一致的。面的真實訪問順序,是保證和程序流的順序是一致的。