本文目標
下圖列舉了 RISC-V 處理器中的通用暫存器:
針對上圖,筆者針對一些可能會有疑問的點進行補充:
x0 暫存器
x0 暫存器的數值為 0 且無法更動,我們可以利用他做到類似 mov 的作用:
add a0 a1, x0 # a0 = a1
Return Address
Return Address 存放函式執行結束時返回的位置,假設,有兩個函式 a, b :
main() -> a()
a() -> b()
因為 main()
呼叫了函式 a()
,在跳進函式 a()
執行前,main()
會將 ra
暫存器的值放到 Stack 上。
此時,如果函式 a()
又呼叫了函式 b()
,在呼叫之前我們會將函式 a()
的 return address 儲存到 Stack,避免 ra 暫存器被函式 b()
複寫後無法返回到 main()
繼續執行。由此可知,ra
由呼叫者維護暫存器的狀態,這類特性的暫存器都是 Caller save。
在這邊如果不考慮 Stack pointer,Stack 內部看起來會是:
+------------------------------+
| Data stored by function b |
+------------------------------+
| Return Address of function a |
+------------------------------+
| Return Address of main |
--+------------------------------+--
Stack pointer
然而,每個 Process 都會有屬於自己的區域變數,這些變數同樣會被存放在 Stack 中,Stack pointer 會指向 Stack 中屬於此 Process 的位址。
不過,這部分與 ra
會些不同,sp
暫存器被定義為 Callee save,當被呼叫的函式修改 sp
暫存器的內容之前,必須先將內容保存下來,並在函式返回前恢復原本的值。
如果以 main()
呼叫 function a()
來看,流程如下:
main() 將當前 ra 的值放到 stack -> 進入 function a() -> 將 sp 的值存放到 stack -> 移動 sp 的位址 (Scope of function a) -> 一些運算 -> 返回到 main 之前恢復 sp 的值 -> 跳回 main() -> 恢復 main() 的 ra
Function args: a0-a7 (x10-x17)
a0-a7 共 8 個暫存器可供我們用於存取函式的參數值,當我們呼叫函式時,函式的參數會依序的存放到這些暫存器中。
Return values: a0-a1
a0, a1 暫存器除了會被當作 args 使用,當函式需要有 return value 時,我們可以將 return value 存放至 a0 與 a1 暫存器,供其他函式取得返回值。
本篇文章介紹了 RISC-V 之中常用的暫存器,包含了:
以上兩點其實就是 RISC-V 的 Calling convention (呼叫慣例),這些規範在編譯器的實作上都能看到,理解它更有助於嵌入式的系統軟體開發。