第4章 陷阱和系統調用

有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硬件對除了計時器中斷外的所有陷阱類型做下面幾件事:

  1. 如果陷阱是一個設備中斷,且sstatus中的SIE標記位被清除了,則什麼都不做;

  2. 清除sstatus中的SIE標記,使中斷失效;

  3. 拷貝pc到spec;

  4. 保存當前模式到sstatus的SPP標記位;

  5. 設置scause寄存器來反映陷阱的起因;

  6. 設置模式爲超級用戶模式;

  7. 拷貝stvec到pc

  8. 跳轉到新的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使用包含uservectrampoline頁來滿足這些約束。xv6將trampoline頁映射到內核頁表和每個用戶頁表中的虛擬地址是相同的。這個虛擬地址就是TRAMPOLINE
trampoline.S中設置了trampoline的內容。
當執行用戶代碼時,設置stevecuservec

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章