RISC V 的中斷與異常處理 - riscv2os/riscv2os GitHub Wiki
CSR 暫存器與指令
CSR 暫存器
RISC-V 架構定義了許多暫存器,部分暫存器被定義為控制和狀態暫存器,也就是標題所指出的 CSR (Control and status registers) ,它被用於配置或是紀錄處理器的運作狀況。 筆者先將 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
CSR 指令
RISC-V 定義了一系列的指令讓開發者能夠對 CSR 暫存器進行操作:
- csrs 把 CSR 中指定的 bit 設為 1。
csrsi mstatus, (1 << 2)
上面的指令會將 mstatus 從 LSB 數起的第三個位置設成 1。
- csrc 把 CSR 中指定的 bit 設為 0。
csrsi mstatus, (1 << 2)
上面的指令會將 mstatus 從 LSB 數起的第三個位置設成 0。
- csrr[c|s] 將 CSR 的值讀入通用暫存器。
csrr to, 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 都必須使用這些操作。
異常
我們都知道,處理器是藉由接收指令進行運作的,若處理器在執行指令流的時候遇到無法預期的情況,便稱之為異常
。
其實異常與中斷非常相似,最主要的差別是異常發生於硬體、程序上的故障。
異常的類型
異常主要分成兩大類型:
-
同步異常
要判斷異常屬於同步或是異步最簡單的方式是: 在同樣的程序以及環境執行 n 次都能將同樣的異常狀態重現,該異常就可以被歸類到同步異常。常見狀況有:
- 非法指令產生的錯誤
- 在 Debugger 設置斷點
- 擷取指令時訪問到非法的地址空間
- 訪問地址的屬性出現錯誤
- 存取指令地址時產生的非對齊錯誤
-
異步異常
異步異常又能在被細分為兩種狀態:
精確的異步異常
以及不精確的異步異常
。 由於簡體文字書籍對於精確/非精確異步異常的介紹相當模糊。因此,筆者直接整理下表方便大家釐清:異常 描述 精確的異步異常 外部中斷 不精確的異步異常 讀寫內存時出錯 !讀寫內存時出錯說明: 常見在當處理器將資料寫進快取後,等到該資料在快取中被替換(存回外部記憶體)時才發現錯誤,此時處理器已經又處理了上百萬條指令。在這個狀況中,我們很難找出罪魁禍首(出錯的指令)是誰。因此,該狀況被歸類在不精確的異步異常。
異常處理
1. Trap
當異常發生時,處理器會停止手邊的工作,再將 Program counter 的位址指向 mtvec 所指的位址並開始執行。這樣的行為就好像是主動跳入陷阱一樣,因此,在 RISC-V 的架構中將這個動作定義為 Trap。
補充: mtvec 是一個可讀可寫的暫存器,開發者可以透過軟體修改其值。
- 當 MODE 為 0 時,轉跳至 BASE 值表示的位址。
- 當 MODE 為 1 時(同步異常),會將 PC 的位址指向: BASE + 4 X CAUSE !這邊的 CAUSE 值等同於 mcause register 所表示的值。
2. 更新 CSR Register (mcause)
在 RISC-V 架構中有定義: 當進入異常時硬體會更新 mcause register 的值,開發者可以透過軟體去讀取 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 的某些域值。
-
MIE 當 MIE 域的值為 1 時,為 Enable,反之為 Disable。
簡單來說,就是決定處理器要不要受理中斷請求。
-
MPIE MPIE 用來存放異常發生前 MIE 域的值。當異常結束後就可以利用 MPIE 還原 MIE 的值。
-
MPP 紀錄異常發生前的工作模式,在 RISC-V 規格書中,有以下幾種模式:
Level Encoding Name Abbreviation 0 00 User/Application U 1 01 Supervisor S 2 10 Reserved - 3 11 Machine M
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 域。
中斷
現今的作業系統都具備多工處理的能力,不管你的電腦是單核心、雙核心甚至是以上,處理器都必須設計有中斷的能力,讓作業系統能指派不同的程式給處理器運行,達成所謂的多工。
關於作業系統如何做到多工,筆者會在之後的作業系統系列提到。
在 RISC-V ISA 的定義中,中斷主要分成以下四種,分別是:
- External Interrupt
- Timer Interrupt
- Software Interrupt
- Dubug Interrupt
RISC-V 其定義了多種模式,如:
Machine Mode
、User Mode
、Supervisor Mode
等等,由於筆者所閱讀的技術書都是以Machine 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 都是被保留的,方便進行擴充。
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
由軟體觸發的中斷,常用於 System call。
該中斷是否屏蔽由 MIE Register 的 MSIE 域控制,等待標誌則反映在 MSIP 域上。
Debug Interrupt
該中斷用於 Debugger 的實作。
中斷屏蔽
異常不能被屏蔽,但是中斷可以。
RISC-V 定義了 MIE Register ,讓它可以控制中斷的屏蔽。
以 MEIE 為例,
- 字首
M
代表 Machine Mode ,字首若是U
及S
則代表User Mode
、Supervisor Mode
。 - 第二個字母
E
代表External Interrupt
,字母若是T
、S
則代表Timer Interrupt
以及Software Interrupt
。 - 第三個字母
I
代表Interrupt
。 - 第四個字母
E
代表Enable
。
中斷等待
RISC-V 定義了 MIP Register ,可以用來查詢中斷的等待狀態。
以 MEIP 為例,
- 字首
M
代表 Machine Mode ,字首若是U
及S
則代表User Mode
、Supervisor Mode
。 - 第二個字母
E
代表External Interrupt
,字母若是T
、S
則代表Timer Interrupt
以及Software Interrupt
。 - 第三個字母
I
代表Interrupt
。 - 第四個字母
P
代表Pending
。
!補充->中斷的優先級: External > Software > Timer
中斷嵌套
中斷嵌套的概念有點像是,進中斷 A 時又產生了中斷 B ,為了幫助讀者,筆者在這邊附上作業系統層的 Nested Interrupt 概念圖:
不過, RISC-V 因為硬體的設計並不支援中斷嵌套,原因如下:
- 進入異常時, mstatus 的 MIE 域會被更新為 0 ,代表中斷被關閉,處理器直到中斷退出前不會處理新的中斷請求。
當然,如果真的有中斷嵌套的需求,開發者也可以利用軟體做到:
- 將 msatus 的 MIE 域設為 1。
- 考慮中斷的優先級,將 MIE Register 相對應的域做設定。