為什麼要關心中斷與異常#

一般在修讀作業系統時,都會學習到中斷(Interrupt)的概念,此外,電腦在運作時也會碰到大大小小的問題,你可曾好奇電腦是如何排除這些問題的呢?本章會介紹 RISC-V 處理器是如何處理中斷以及異常(Exception)的。

RISC-V 擁有不同的特權模式(Privilege Mode),供不同層級的程式運作在對應的平台上,如 Machine Mode、User Mode、Supervisor Mode 等等。本章只會專注在具有完整硬體存取權限的 Machine Mode 上,如果讀者對特權模式有興趣,可以參考 RISC-V 的官方文件 riscv-privileged-v1.10

CSR 暫存器#

RISC-V 架構定義了許多暫存器,部分暫存器被定義為控制和狀態暫存器,也就是 CSR (Control and Status Register),它被用於配置或是紀錄處理器的運作狀況。

以下先把 RISC-V 架構中(Machine Mode)與中斷、異常有關的暫存器都列出來,方便待會兒進行解說:

  • CSR
    • mtvec:當進入異常時,PC(Program Counter)會進入 mtvec 所指向的位址並繼續運行。
    • mcause:紀錄異常的原因。
    • mtval:紀錄異常訊息。
    • mepc:進入異常前 PC 所指向的位址。若異常處理完畢,Program Counter 可以讀取該位址並繼續執行。
    • mstatus:進入異常時,硬體會更新 mstatus 寄存器的某些域值。
    • mie:決定中斷是否被處理。
    • mip:反映不同類型中斷的等待狀態。
  • Memory Address Mapped
    • mtime:紀錄計時器的值。
    • mtimecmp:儲存計時器的比較值。
    • msip:產生或結束軟體中斷。
    • PLIC (Platform-Level Interrupt Controller)

異常(Exception)#

我們都知道,處理器是藉由接收指令進行運作的,若處理器在執行指令流的時候遇到無法預期的情況,便稱之為「異常」。

其實異常與中斷非常相似,最主要的差別是異常發生於硬體、程式上的故障。

異常的類型#

異常主要分成兩大類型:

同步異常#

要判斷異常屬於同步或是異步,最簡單的方式是:在同樣的程式以及環境執行 n 次都能將同樣的異常狀態重現,該異常就可以被歸類到同步異常。常見狀況有:

  • 非法指令產生的錯誤
  • 在 Debugger 設置斷點
  • 擷取指令時存取到非法的位址空間
  • 存取位址的屬性出現錯誤
  • 存取指令位址時產生的非對齊錯誤

異步異常#

異步異常又能被細分為兩種狀態:精確的異步異常以及不精確的異步異常。

異常描述
精確的異步異常外部中斷
不精確的異步異常讀寫記憶體時出錯

讀寫記憶體時出錯說明: 常見在當處理器將資料寫進快取後,等到該資料在快取中被替換(存回外部記憶體)時才發現錯誤,此時處理器已經又處理了上百萬條指令。在這個狀況中,我們很難找出罪魁禍首(出錯的指令)是誰。因此,該狀況被歸類在不精確的異步異常。

異常處理流程#

1. Trap#

當異常發生時,處理器會停止手邊的工作,再將 Program Counter 的位址指向 mtvec 所指的位址並開始執行。這樣的行為就好像是主動跳入陷阱一樣,因此,在 RISC-V 的架構中將這個動作定義為陷阱(Trap)。

補充:mtvec 是一個可讀可寫的暫存器,開發者可以透過軟體修改其值。

mtvec 的格式:

mtvec format

mtvec MODE

  • 當 MODE 為 0 時,轉跳至 BASE 值表示的位址。
  • 當 MODE 為 1 時(同步異常),會將 PC 的位址指向:
    • BASE + 4 × CAUSE
    • 這邊的 CAUSE 值等同於 mcause register 所表示的值。

2. 更新 CSR Register(mcause)#

在 RISC-V 架構中有定義:當進入異常時硬體會更新 mcause register 的值。開發者可以透過軟體去讀取 mcause 以分析造成異常的具體原因。

mcause

mcause 的最高位(MSB)用來記錄是否為 interrupt,其餘 31 位都用來表示異常編號。

3. 更新 CSR Register(mepc)#

mepc register 被用來存放跳入 Trap 前 PC 指向的指令位址,待異常處理結束後,PC 會將 mepc 存放的指令位址讀入並開始執行。

需要注意的是,mepc 在中斷和異常發生時會有不同的行為:

  • 異常發生時:mepc 會記錄 Program Counter 指向的指令位址,也就是出現異常的那條指令。
  • 中斷發生時:mepc 會記錄 Program Counter 指向的位址的下一個指令位址,例如:
    • 中斷發生!(PC = 0x1c)
    • mepc = PC + 4
    • 異常處理
    • 處理完成,跳回正常狀態(PC = mepc)
  • 特例:若異常是由 ecall 或是 ebreak 造成的,則參照中斷發生時的情況處理。否則會造成 ecall 以及 ebreak 重複呼叫形成無窮迴圈。

關於 ecall 以及 ebreak,請參考 RV32I 指令集。 補充:mepc register 也是一個可讀可寫的暫存器,因此我們同樣可以利用軟體修改它的值。

4. 更新 CSR Register(mtval)#

mtval register 又稱為 mbadaddr register,在 RISC-V 的規格書有定義:

當進入異常時,硬體會自動更新 mtval register 的值,以紀錄造成異常發生的暫存器存取位址或是指令編碼。

從上面的敘述就可以知道,mtval 會有兩種寫入的 Case,分別是:

  • 暫存器存取造成的異常
    • 包括如硬體斷點、存取指令、儲存器讀寫時造成的異常。這時,硬體會將儲存器存取的位址記錄到 mtval 當中。
  • 非法指令造成的異常
    • 除了 RVC 指令集外,其他合法 RISC-V 指令集的 OPCODE 末兩碼都是 11。
    • 在這個情況中,硬體會將非法的指令編碼記錄到 mtval 當中。

5. 更新 CSR Register(mstatus)#

mstatus 紀錄了大量的資料。根據 RISC-V 架構的規定:當進入異常時,硬體會自動更新 mstatus 的某些域值。

format of mstatus

  • MIE:當 MIE 域的值為 1 時為 Enable,反之為 Disable。

簡單來說,就是決定處理器要不要受理中斷請求。

  • MPIE:用來存放異常發生前 MIE 域的值。當異常結束後就可以利用 MPIE 還原 MIE 的值。
  • MPP:紀錄異常發生前的工作模式。在 RISC-V 規格書中,有以下幾種模式:
LevelEncodingNameAbbreviation
000User/ApplicationU
101SupervisorS
210Reserved-
311MachineM

6. 退出異常狀態#

當異常處理完成後,需要從異常服務退出。在 RISC-V 架構中定義了一組用於退出異常的指令(Trap-Return Instruction),包括:

  • MRET
  • SRET
  • URET

分別對應了 Machine Mode、Supervisor Mode 以及 User Mode。

使用 MRET 指令退出異常後,硬體會做兩件事情:

  • 從 mepc 指向的指令位址開始執行。
  • 更新 mstatus register。同樣以 Machine Mode 為例:
    • 將 MIE 更新為 MPIE 的值。
    • 將 MPIE 域的值更新為 1。

需要注意的是:MIE 域僅是反映中斷是否接受處理,其控制權仍取決於 MIE register 中的 MEIE 域。

中斷(Interrupt)#

現今的作業系統都具備多工處理的能力,不管你的電腦是單核心、雙核心甚至是以上,處理器都必須設計有中斷的能力,讓作業系統能指派不同的程式給處理器運行,達成所謂的「多工」。

在 RISC-V ISA 的定義中,中斷主要分成以下四種:

  • External Interrupt
  • Timer Interrupt
  • Software Interrupt
  • Debug Interrupt

RISC-V 定義了多種模式,如 Machine Mode、User Mode、Supervisor Mode 等等。本章節同樣以 Machine Mode 為主。

External Interrupt#

External Interrupt 是指源自處理器外部造成的中斷,像是 UART、GPIO 等外部設備產生的中斷。

通常因為有多個外部設備,在處理器外面會有一個控制器先將外部的中斷請求做預處理,等到處理完成後,若處理器沒有關閉 Interrupt 的功能,處理器才會真正執行中斷。這個外部控制器叫做 Programmable Interrupt Controller。此外,RISC-V 也定義了 Platform Level Interrupt,細節會在下一章補充。

補充:RISC-V 架構對中斷的定義保留了很大的空間,以 mcause register 為例,在 Interrupt 的定義上,Exception Code >= 12 都是被保留的,方便進行擴充。

mcause

Timer Interrupt#

RISC-V 架構有規定,系統平台必須要有一個計時器。並且,該計時器必須具備兩個 64-bit 的暫存器 mtime 以及 mtimecmp,前者用於紀錄當前計數器的值,後者則是 mtime 的比較值。當 value of mtime > value of mtimecmp 時便會產生中斷。

補充:在 CSR Register 的介紹中可以看到,mtime 以及 mtimecmp 並沒有被歸類在 CSR 暫存器中,而是被放在 Memory address mapped 類,這是因為 RISC-V 並沒有定義這兩個暫存器的 Memory mapped address,具體位址交給 SoC 的系統製造商決定。

Software Interrupt#

軟體觸發的中斷。

  • 軟體中斷是否被系統受理由 MIE Register 的 MSIE 域控制(1 為 Enable,0 為 Disable)。
  • 若硬體執行緒將 MSIP 寫入 1 會觸發 Machine Mode 的軟體中斷,當中斷處理完畢後,必須再將 MSIP 歸 0。

Debug Interrupt#

該中斷用於 Debugger 的實作。

中斷屏蔽#

異常不能被屏蔽,但是中斷可以。

RISC-V 定義了 MIE Register,讓它可以控制中斷的屏蔽。

MIE

以 MEIE 為例:

  • 字首 M 代表 Machine Mode,字首若是 US 則代表 User Mode、Supervisor Mode。
  • 第二個字母 E 代表 External Interrupt,字母若是 TS 則代表 Timer Interrupt 以及 Software Interrupt。
  • 第三個字母 I 代表 Interrupt。
  • 第四個字母 E 代表 Enable。

中斷等待#

RISC-V 定義了 MIP Register,可以用來查詢中斷的等待狀態。

MIP

以 MEIP 為例:

  • 字首 M 代表 Machine Mode,字首若是 US 則代表 User Mode、Supervisor Mode。
  • 第二個字母 E 代表 External Interrupt,字母若是 TS 則代表 Timer Interrupt 以及 Software Interrupt。
  • 第三個字母 I 代表 Interrupt。
  • 第四個字母 P 代表 Pending。

補充:中斷的優先級為 External > Software > Timer。

巢狀中斷#

中斷嵌套的概念有點像是,進入中斷 A 時又產生了中斷 B:

Nested Interrupt

不過,RISC-V 處理器在預設情況下並不支援中斷嵌套,原因如下:

  • 進入異常時,mstatus 的 MIE 域會被更新為 0,代表中斷被關閉,處理器直到中斷退出前不會處理新的中斷請求。

當然,如果真的有中斷嵌套的需求,開發者也可以利用軟體做到:

  • 將 mstatus 的 MIE 域設為 1。
  • 考慮中斷的優先級,將 MIE Register 相對應的域做設定。

CSR 指令#

RISC-V 定義了一系列的指令讓開發者能夠對 CSR 暫存器進行操作:

  • csrs:把 CSR 中指定的 bit 設為 1。
csrsi mstatus, (1 << 2)

上面的指令會將 mstatus 從 LSB 數起的第三個位置設成 1。

  • csrc:把 CSR 中指定的 bit 設為 0。
csrci mstatus, (1 << 2)

上面的指令會將 mstatus 從 LSB 數起的第三個位置設成 0。

  • csrr[c|s]:將 CSR 的值讀入通用暫存器。
csrr t0, mscratch
  • csrw:將通用暫存器的值寫入 CSR。
csrw mepc, a0
  • csrrw[i]:將 csr 的值寫入 rd 後,且將 rs1 的值寫入 csr。
csrrw rd, csr, rs1/imm

換個角度思考:

csrrw t6, mscratch, t6

上面的操作可以讓 t6 與 mscratch 的值互換。

這幾個 CSR 指令需要開發者特別留意,之後在閱讀甚至是撰寫作業系統原始碼時,在處理各類的 Interrupt 都必須使用這些操作。

原文出處#