2 1 RISC V::關於基本暫存器 - ianchen0119/AwesomeCS GitHub Wiki

本文目標

  • 認識暫存器
  • 建立 Callee save 與 Caller save 的觀念
  • 了解呼叫慣例

進入正題

下圖列舉了 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 (呼叫慣例),這些規範在編譯器的實作上都能看到,理解它更有助於嵌入式的系統軟體開發。

Reference