XV6學習(5)陷阱和系統調用

在操做系統中,有三種狀況會致使CPU的控制流發生轉移:用戶態中經過ecall指令進入內核態;異常發生,如除零、訪問非法地址;設備中斷,如硬盤完成讀寫請求。上面這些狀況能夠統稱爲陷阱(trap)。數組

陷阱在通常狀況下應該是透明的,即當執行完處理程序後可以恢復以前程序的狀態。這就要求在陷入內核態時,內核要保存以前的寄存器等狀態信息,當執行完處理程序以後再進行恢復。app

在XV6中處理陷阱有如下四步:CPU進行硬件操做,彙編向量被設置,C陷阱處理程序決定如何處理,系統調用或設備驅動處理該陷阱。內核中一般分三種狀況來分別處理這些陷阱:用戶態陷阱、內核態陷阱、時鐘中斷。函數

RISC-V CPU有一系列控制寄存器來決定如何處理陷阱,這些寄存器是由內核來設置的。操作系統

  • stvec:陷阱處理程序入口,CPU會跳轉到此處來處理陷阱
  • sepc:保存陷阱發生時的pc,使用sret指令會將pc恢復
  • scause:陷阱緣由
  • sscratch:內核保存特定的值,見下文
  • sstatussstatus中的SIE位控制中斷是否容許;SPP位表示陷阱來自用戶模式仍是監管模式。

當發生陷阱時,硬件會進行如下操做:設計

  1. 若是是設備中斷,而且SIE是清空的,就不響應
  2. 清空SIE以關閉中斷
  3. 保存pcsepc
  4. 保存當前模式到SPP
  5. 設置scause
  6. 切換到監管模式
  7. 拷貝stvecpc
  8. 開始執行處理程序

硬件不會自動切換內核頁表和內核棧,也不會保存除pc之外的寄存器,處理程序必須完成上述工做。這樣設計能夠給軟件更好的靈活性。而設置pc的工做必須由硬件完成,由於當切換到內核態時,用戶指令可能會破壞隔離性。指針

用戶態陷阱

XV6的用戶態陷阱處理流程以下:uservec -> usertrap -> usertrapret -> userretcode

因爲CPU不會進行頁表切換,所以用戶頁表必須包含uservec函數(stvec所指向的函數)的映射。該函數要將satp切換爲內核頁表,爲了切換後的指令能繼續執行,該函數必須在用戶頁表和內核頁表中有相同的地址。爲了知足上述要求,XV6將一個叫trampoline的頁映射到相同的虛擬地址TRAMPOLINE,其中包含了trampoline.S的指令,並設置stvecuservec進程

uservec

在進入uservec函數時,全部的32個寄存器都是被中斷代碼所享有的,而uservec須要使用寄存器來執行指令,所以,RISC-V提供了sscratch寄存器,經過csrrw a0, sscratch, a0指令,保存a0,以後就可使用a0寄存器了。內存

以後,函數就須要保存全部用戶寄存器到trapframe結構體中,該結構體的地址在進入用戶模式以前,被保存在sscratch寄存器中,所以通過以前的csrrw操做後,就被保存在a0中。當建立進程時,內核會申請一個頁面保存trapframe,該頁面就位於TRAMPOLINE下方,進程的p->trapframe也指向該頁面。it

最後,函數從trapframe中取出內核棧地址、hartid、usertrap的地址、內核頁表地址,切換頁表,跳轉到usertrap函數。

usertrap

usertrap的工做即判斷陷阱類型並處理,最後返回。函數首先將stvec設置爲kernelvec的地址,使內核態發生的中斷由kernelvec函數來處理。以後保存sepc寄存器,防止其被覆蓋。而後判斷陷阱類型,若是是系統調用,就將pc指向ecall的下一條指令,而後交給syscall函數處理;若是是設備中斷,就交給devintr;不然就是異常,那麼就終止該進程的運行。在最後會判斷進程是否已經被殺死或者當發生時鐘中斷時,讓出處理器。

usertrapret

該函數首先將stvec設置爲uservec的地址,以後設置trapframe(這些內容在uservec中會使用到),而後恢復sepc寄存器。最後,調用userret函數。

最後,在userret函數中進行與uservec相反的步驟,將頁表和寄存器進行恢復。

系統調用

initcode.S中的系統調用爲例,將兩個參數分別放在a0 a1寄存器中,將系統調用號放在a7寄存器中,而後執行ecall指令。

# exec(init, argv)
.globl start
start:
        la a0, init
        la a1, argv
        li a7, SYS_exec
        ecall

而在syscall函數中,會取出a7的值,而後查找syscalls數組,找到相應的處理函數即sys_exec,交由該函數進行處理,最後將返回值放在trapframe->a0中。

內核態陷阱

內核態陷阱的處理路徑爲:kernelvec -> kerneltrap -> kernelvec

kernelvec

因爲陷阱發生在內核態,所以,不須要對satp和棧指針進行處理,只須要保存全部通用寄存器便可。以後跳轉到kerneltrap進行處理,當該函數返回後,再恢復所保存的寄存器。

kerneltrap

kerneltrap只須要處理兩種陷阱:設備中斷和異常。經過調用devintr判斷是否爲設備中斷,若是不是設備中斷,那麼就是異常,且該異常發生在內核態,內核調用panic函數終止執行。若是是時鐘中斷,那麼就讓出處理器。因爲yield函數會致使sepc sstatus寄存器被修改,所以在kerneltrap中要對其進行保存和恢復。

缺頁異常

在XV6中,並無對異常進行處理,僅僅是簡單地kill或panic。而在真實操做系統中,會對異常進行具體的處理。例如使用缺頁異常來實現COW(copy on write)fork。

在RISC-V中,有三種不一樣的缺頁異常:load page faults(當load指令轉換虛擬地址時發生),store page faults(當store指令轉換虛擬地址時發生),instruction page faults(當指令的地址轉化時發生)。在scause寄存器中保存了異常緣由,stval中保存了轉換失敗的地址。

COW fork使子進程與父進程享有相同的物理頁面,可是設置爲只讀的。當子進程或父進程執行store指令時,就會觸發異常,此時再對頁面進行拷貝,而後以讀寫的模式映射到父子進程的地址空間。

另外一種技術是lazy allocation,當應用調用sbrk時,增加地址空間,但在頁表中標記新地址爲無效的。當在新地址上發生缺頁異常後,才真正地分配物理頁面給進程。

paging from disk即虛擬內存,操做系統選擇一部分保存到磁盤上並標記頁表項爲無效,當讀寫該頁面時再從磁盤中取回內存。除此以外,還有如automatically extending stacks 和 memory-mapped files等技術也使用了缺頁異常。

相關文章
相關標籤/搜索