本文章是對 從零開始寫 OS (4) —— Trap 的補充,如果沒有看過的話最好先去看看。沒看過的話也沒有影響,可以作為獨立的文章閱讀。

__alltrap

當操作系統產生中斷時, cpu 會跳轉到 stvec 控制寄存器中保存的地址。在 interrupt::init 中,我們設置這個地址為 __alltrap

.section .text
.globl __alltraps
__alltraps:
SAVE_ALL
mv a0, sp
jal rust_trap
.globl __trapret
__trapret:
RESTORE_ALL
# return from supervisor call
sret

.globl 表示該符號是一個全局符號,可以被外部訪問到。 .section .text__alltraps__trapret 放到程序的同一個代碼段中,使得 __alltrap 執行完後會立刻執行 __trapret 。在使用 SAVE_ALL 保存了所有寄存器狀態後,將 棧幀地址(sp) 賦值給 a0a0 是參數寄存器, jal 調用的函數(rust_trap)將把 a0 中的內容作為參數。在處理完中斷後,由 __trapret 將寄存器恢復到中斷前的狀態,繼續執行中斷前的指令。

SAVE_ALL

保存寄存器的工作只能在 內核態堆棧 進行。如果當前處於 用戶態堆棧 ,則需要先進入 內核態堆棧 ,再保存寄存器:

.macro SAVE_ALL
# If coming from userspace, preserve the user stack pointer and load
# the kernel stack pointer. If we came from the kernel, sscratch
# will contain 0, and we should continue on the current stack.
csrrw sp, sscratch, sp
bnez sp, _save_context
_restore_kernel_sp:
csrr sp, sscratch
# sscratch = previous-sp, sp = kernel-sp
_save_context:
# provide room for trap frame
addi sp, sp, -36*XLENB
# save x registers except x2 (sp)
STORE x1, 1
STORE x3, 3
# tp(x4) = hartid. DONT change.
# STORE x4, 4
STORE x5, 5
STORE x6, 6
STORE x7, 7
STORE x8, 8
STORE x9, 9
STORE x10, 10
STORE x11, 11
STORE x12, 12
STORE x13, 13
STORE x14, 14
STORE x15, 15
STORE x16, 16
STORE x17, 17
STORE x18, 18
STORE x19, 19
STORE x20, 20
STORE x21, 21
STORE x22, 22
STORE x23, 23
STORE x24, 24
STORE x25, 25
STORE x26, 26
STORE x27, 27
STORE x28, 28
STORE x29, 29
STORE x30, 30
STORE x31, 31

# get sp, sstatus, sepc, stval, scause
# set sscratch = 0
csrrw s0, sscratch, x0
csrr s1, sstatus
csrr s2, sepc
csrr s3, stval
csrr s4, scause
# store sp, sstatus, sepc, sbadvaddr, scause
STORE s0, 2
STORE s1, 32
STORE s2, 33
STORE s3, 34
STORE s4, 35
.endm

中斷髮生時,操作系統進入內核態,但是此時所有寄存器都沒改變(包括sp)。這就導致了一個麻煩的問題:如何在不破壞32個寄存器的情況下,判斷是不是用戶態中斷,並切換 sp 到內核棧,然後保存寄存器。

解決問題的關鍵是要有一個可做交換操作的臨時寄存器,這裡就是 sscratch 。我們規定當操作系統處於用戶態時, sscratch 保存內核棧地址;處於內核態時,sscratch 為0。於是中斷處理的第一條指令就是交換 spsscratch ,這樣就可以根據sp判斷是不是用戶態中斷。如果是用戶態中斷,此時 sp 已經是內核棧了;如果是內核態中斷,就再從 sscratch 中讀出sp(注意不是交換)。

這樣一番操作後,使得 sp 指向內核棧, sscratch 指向中斷前的棧(U/S)。

RESTORE_ALL

在恢復寄存器則與保存的過程相反。首先通過 sstatus 判斷中斷前操作系統所處狀態。若為用戶態,則需要 sscratch 的值:

.macro RESTORE_ALL
LOAD s1, 32 # s1 = sstatus
LOAD s2, 33 # s2 = sepc
andi s0, s1, 1 << 8 # sstatus.SPP = 1?
bnez s0, _to_kernel # s0 = back to kernel?
_to_user:
addi s0, sp, 36*XLENB
csrw sscratch, s0 # sscratch = kernel-sp
_to_kernel:
# restore sstatus, sepc
csrw sstatus, s1
csrw sepc, s2

# restore x registers except x2 (sp)
LOAD x1, 1
LOAD x3, 3
# LOAD x4, 4
LOAD x5, 5
LOAD x6, 6
LOAD x7, 7
LOAD x8, 8
LOAD x9, 9
LOAD x10, 10
LOAD x11, 11
LOAD x12, 12
LOAD x13, 13
LOAD x14, 14
LOAD x15, 15
LOAD x16, 16
LOAD x17, 17
LOAD x18, 18
LOAD x19, 19
LOAD x20, 20
LOAD x21, 21
LOAD x22, 22
LOAD x23, 23
LOAD x24, 24
LOAD x25, 25
LOAD x26, 26
LOAD x27, 27
LOAD x28, 28
LOAD x29, 29
LOAD x30, 30
LOAD x31, 31
# restore sp last
LOAD x2, 2
.endm

代碼總覽

將上述所有代碼整理好後,我們的 trap.asm 應該長成這樣:

# Constants / Macros defined in Rust code:
# XLENB
# LOAD
# STORE

.macro SAVE_ALL
# If coming from userspace, preserve the user stack pointer and load
# the kernel stack pointer. If we came from the kernel, sscratch
# will contain 0, and we should continue on the current stack.
csrrw sp, sscratch, sp
bnez sp, _save_context
_restore_kernel_sp:
csrr sp, sscratch
# sscratch = previous-sp, sp = kernel-sp
_save_context:
# provide room for trap frame
addi sp, sp, -36*XLENB
# save x registers except x2 (sp)
STORE x1, 1
STORE x3, 3
# tp(x4) = hartid. DONT change.
# STORE x4, 4
STORE x5, 5
STORE x6, 6
STORE x7, 7
STORE x8, 8
STORE x9, 9
STORE x10, 10
STORE x11, 11
STORE x12, 12
STORE x13, 13
STORE x14, 14
STORE x15, 15
STORE x16, 16
STORE x17, 17
STORE x18, 18
STORE x19, 19
STORE x20, 20
STORE x21, 21
STORE x22, 22
STORE x23, 23
STORE x24, 24
STORE x25, 25
STORE x26, 26
STORE x27, 27
STORE x28, 28
STORE x29, 29
STORE x30, 30
STORE x31, 31

# get sp, sstatus, sepc, stval, scause
# set sscratch = 0
csrrw s0, sscratch, x0
csrr s1, sstatus
csrr s2, sepc
csrr s3, stval
csrr s4, scause
# store sp, sstatus, sepc, sbadvaddr, scause
STORE s0, 2
STORE s1, 32
STORE s2, 33
STORE s3, 34
STORE s4, 35
.endm

.macro RESTORE_ALL
LOAD s1, 32 # s1 = sstatus
LOAD s2, 33 # s2 = sepc
andi s0, s1, 1 << 8 # sstatus.SPP = 1?
bnez s0, _to_kernel # s0 = back to kernel?
_to_user:
addi s0, sp, 36*XLENB
csrw sscratch, s0 # sscratch = kernel-sp
_to_kernel:
# restore sstatus, sepc
csrw sstatus, s1
csrw sepc, s2

# restore x registers except x2 (sp)
LOAD x1, 1
LOAD x3, 3
# LOAD x4, 4
LOAD x5, 5
LOAD x6, 6
LOAD x7, 7
LOAD x8, 8
LOAD x9, 9
LOAD x10, 10
LOAD x11, 11
LOAD x12, 12
LOAD x13, 13
LOAD x14, 14
LOAD x15, 15
LOAD x16, 16
LOAD x17, 17
LOAD x18, 18
LOAD x19, 19
LOAD x20, 20
LOAD x21, 21
LOAD x22, 22
LOAD x23, 23
LOAD x24, 24
LOAD x25, 25
LOAD x26, 26
LOAD x27, 27
LOAD x28, 28
LOAD x29, 29
LOAD x30, 30
LOAD x31, 31
# restore sp last
LOAD x2, 2
.endm

.section .text
.globl __alltraps
__alltraps:
SAVE_ALL
mv a0, sp
jal rust_trap
.globl __trapret
__trapret:
RESTORE_ALL
# return from supervisor call
sret

推薦閱讀:

相关文章