有3類事件可導致CPU把普通的指令執行擱置在一邊,強制把控制權轉移到能處理事件的特定代碼處。
- 系統調用
用戶程序執行ecall
指令來請求內核爲它做一些事; - 異常
一條指令(用戶或者內核)做了非法的事,比如除以0、使用了一個非法的虛擬地址等; - 設備中斷
設備發出了需要關注的信號,比如磁盤完成了讀或者寫操作等
本書中使用陷阱trap作爲這3種情形的泛稱。
當陷阱出現時,無論正在執行什麼代碼都需要恢復,不應該感知到發生了任何特殊的事件。
即,我們通常希望陷阱是透明的,這對於中斷來說極其重要,因爲被中斷的代碼不期望感知到有特殊事件發生了。
通常的步驟是:
- 陷阱強制將控制權轉交給內核;
- 內核保存寄存器及其他狀態,使得執行能被恢復;
- 內核執行合適的處理代碼(比如,系統調用的實現或者設備驅動);
- 內核恢復被保存的狀態,從陷阱中返回;
- 從被打斷處恢復原始代碼的執行;
xv6內核處理所有類型的陷阱。
對系統調用來說,這是很自然的事。
對中斷來說,也是很有意義的,因爲隔離性要求:用戶進程不能直接訪問設備,只有內核有處理設備所需的狀態。
對異常來說,也很有意義,因爲xv6對來自用戶空間的所有異常的作出的響應是殺掉相應的進程。
xv6的陷阱處理分爲4個階段:
- RISC-V的CPU採取的硬件操作;
- 1個爲內核C代碼準備好路徑的彙編向量;
- 1個決定如何處理陷阱的C陷阱處理程序;
- 系統調用或者設備驅動服務例程;
雖然這3類陷阱之間的共性建議:內核可使用一條代碼路徑來處理所有類型的陷阱,但是事實證明,針對用戶空間陷阱、內核空間陷阱、計時器中斷等3種不同的情形,有單獨的彙編向量及C陷阱處理程序是很方便的。
第4.1節 RISV的陷阱機制
每個RISC-V的CPU都有一套控制寄存器,內核可向其中寫入信息來告知CPU如何處理陷阱,內核可從中讀數據來查找有關已發生的陷阱信息。
在riscv.h
中包含了xv6使用的定義。
- stvec寄存器
內核向其中寫入中斷處理程序的地址;
RISC-V將跳轉到這裏記錄的地址處理陷阱; - sepc寄存器
當陷阱發生時,RISC-V將程序計數器的值保存在這裏,因爲隨後pc的值將被stvec的值覆蓋掉;
sret指令拷貝sepc的值到pc中;
內核可向spec中寫入值來控制sret返回到哪裏; - scause寄存器
RISC-V在這裏放入一個數,描述的是陷阱發生的原因; - sscratch寄存器
內核在這裏放置一個值,這個值會在處理程序開始時很有用; - sstatus寄存器
在sstatus中的SIE位控制的是設備中斷是否生效;
如果內核清除了SIE,則RISC-V將延遲設備中斷直到內核設置了SIE。
在sstatus中的SPP位記錄的是陷阱來自用戶模式還是超級用戶模式,及控制sret返回到哪種模式。
以上5個跟陷阱相關的寄存器都是在超級用戶模式下處理的,在用戶模式下不能讀寫這5個寄存器的值。
在機器模式下,有等價的一套控制寄存器來用於陷阱處理。
xv6僅在計時器中斷的特殊情況下使用這些寄存器。
在多核處理器的每個CPU都有自己的一套類似這樣的寄存器;在任意給定時刻,可能不止有一個CPU在處理陷阱。
當需要強制處理一個陷阱時,RISV硬件對除了計時器中斷外的所有陷阱類型做下面幾件事:
如果陷阱是一個設備中斷,且sstatus中的SIE標記位被清除了,則什麼都不做;
清除sstatus中的SIE標記,使中斷失效;
拷貝pc到spec;
保存當前模式到sstatus的SPP標記位;
設置scause寄存器來反映陷阱的起因;
設置模式爲超級用戶模式;
拷貝stvec到pc
跳轉到新的pc處開始執行
注意:
CPU沒有切換到內核的頁表,沒有切換到內核棧中,沒有保存除了pc之外的任何寄存器。這些是內核軟件必須要做的任務。
理由:CPU在處理陷阱的過程中做少量的工作是爲了給軟件提供更大的靈活性。比如,一些操作系統在某些情況下不需要頁表切換的,這可以提升性能。
能不能對CPU的陷阱處理步驟進行進一步的簡化?
假設CPU不切換程序寄存器pc。
則還在運行用戶指令,陷阱就切換到超級用戶模式了。這樣,那些用戶指令就能破壞用戶/內核隔離機制了,比如通過修改satp寄存器來指向允許訪問整個物理內存的頁表了。
因此,CPU切換到由stvec寄存器指定的內核指令地址是非常重要的。
4.2 來自用戶空間的陷阱
當CPU在用戶空間執行時,如果用戶程序做了一個系統調用,或者做了非法的事,或者某個設備中斷了,則就可能會發生一個陷阱。
處理來自用戶空間的陷阱的代碼路徑是先uservec
,後usertrap
。
返回時是先usertrapret
,後userret
。
來自用戶空間的陷阱處理代碼要比來自內核的更具有挑戰性,因爲satp
指向的是一個沒有映射到內核的用戶頁表,棧指針可能包含一個無效甚至是惡意的值。
因爲RISC-V硬件在陷阱期間不切換頁表,則用戶頁表必須包含uservec
的映射,即stvec
指向的陷阱向量指令。
uservec
必須切換satp
以指向內核頁表;爲了在切換後繼續執行指令,必須將uservec
映射到內核頁表中跟用戶頁表中相同的地址。
xv6使用包含uservec
的trampoline頁
來滿足這些約束。xv6將trampoline頁
映射到內核頁表和每個用戶頁表中的虛擬地址是相同的。這個虛擬地址就是TRAMPOLINE
。
在trampoline.S
中設置了trampoline
的內容。
當執行用戶代碼時,設置stevec
爲uservec
。