系統呼叫跳轉流程
以write()系統呼叫為例
1. 使用者態
-
呼叫
write()
-
跳轉到
usys.s\write
#include "kernel/syscall.h" write: li a7, SYS_write ecall ret
SYS_write
的定義在kernel/syscall.h
中#define SYS_write 16
將
SYS_write
的索引16
放到a7暫存器
中執行
ecall
,跳轉到STVEC
指向的地址!!!
STVEC
涉及到中斷相關知識,後續補齊!!!???
STVEC
中的地址在哪裡設定的此處
STVEC
指向trampoline.S\uservec()
Q1.ECALL功能?
- 從
user mode
切換到supervisor mode
- 將
pc
的值儲存到SEPC
暫存器 - 跳轉到
STVEC
指向的地址
ecall的功能十分簡單,主要是為了提升軟體設計的靈活性,但此時ecall
做的事遠遠不夠,對於xv6,我們還需要完成
- 儲存31個使用者暫存器的內容,之後用來恢復程式碼執行狀態
ecall
不切換page table
,因此此時還在使用user page table
,需要切換到kernel page table
。- 需要建立或者找到一個
kernel stack
,並將Stack Pointer
暫存器的內容指向這個kernel stack
。程式碼需要使用棧執行程式。 - 需要跳轉到核心程式碼的某些合理的位置。
使用ECALL指令時,將系統呼叫型別存在a7暫存器,引數存在a0-a5暫存器
2 trampoline.s\uservec()
儲存32個通用暫存器
uservec:
# 需要儲存所有暫存器的值,之後需要恢復執行
# 此時所有通用暫存器都不可修改
# 將`a0`儲存到`sscratch`,這樣`a0`就可以修改了
csrw sscratch, a0
# 將`TRAPFRAME`放到`a0`中,給後續儲存提供基地址
li a0, TRAPFRAME
# 儲存其他通用暫存器
sd ra, 40(a0)
sd sp, 48(a0)
sd gp, 56(a0)
sd tp, 64(a0)
....
# 將之前儲存在`sscratch`中的`a0`取出並儲存
csrr t0, sscratch
sd t0, 112(a0)
# 載入`TRAPFRAME`中的`kernel_sp`到`stack pointer`暫存器中
ld sp, 8(a0)
# 載入`kernel_hartid`(當前CPU編號)到`tp`暫存器
ld tp, 32(a0)
# 載入`usertrap()`地址到`t0`暫存器
ld t0, 16(a0)
# 載入`kernel page table`到`t1`暫存器中
ld t1, 0(a0)
# 等待之前記憶體訪問完成,清空tle
sfence.vma zero, zero
# 載入`t1`到`satp`,頁錶轉換為核心頁表
csrw satp, t1
sfence.vma zero, zero
# jump to usertrap(), which does not return
jr t0
??? t0在哪裡設定的
3 trap.c\usertrap()
確定trap型別並進行處理
void usertrap(void) {
int which_dev = 0;
// 獲取`sstatus`暫存器的值,`SPP`位指示`trap`是來自
// 使用者模式還是管理模式
if((r_sstatus() & SSTATUS_SPP) != 0)
panic("usertrap: not from user mode");
// 令`stvec`指向`kernelvec`而不是之前的`uservec`
// 在kernel中,trap處理不必使用user模式的邏輯
w_stvec((uint64)kernelvec);
// myproc()根據之前儲存的hart_id值,查詢程序陣列
// 獲取當前程序資訊
struct proc *p = myproc();
// 將`ecall`指令儲存在`sepc`中的`pc`值儲存到`trapframe`中
p->trapframe->epc = r_sepc();
//獲取`scause`的值,判斷trap型別
if(r_scause() == 8){
// system call
// 判斷當前程序是否被殺掉
if(killed(p))
exit(-1);
// pc儲存的是系統呼叫的地址,返回時需指向下一條指令
p->trapframe->epc += 4;
// 修改`sstatus`的SIE位,開啟中斷,trap執行過程中可響應中斷
intr_on();
// 根據之前儲存在`p->trapframe->a7`中的值找到系統呼叫並執行
syscall();
} else if((which_dev = devintr()) != 0){
// ok
} else {
printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid);
printf(" sepc=%p stval=%p\n", r_sepc(), r_stval());
setkilled(p);
}
// 如果程序已關閉,就不必恢復了
if(killed(p))
exit(-1);
// give up the CPU if this is a timer interrupt.
if(which_dev == 2)
yield();
usertrapret();
}
Q1 為什麼需要儲存sepc
中的pc
值?
當前程式執行時,CPU可能切換到別的程序,而別的程序如果也進行系統呼叫,sepc暫存器就會被修改,這個操作可以放到之前的trampoline.s\uservec()
中,邏輯更統一。
?scause
是何時被設定的
ecall
指令用於向執行時環境發出請求,如系統呼叫,因此應該是ecall設定的
4.1.4 trap.c\usertrapret()
void usertrapret(void)
{
struct proc *p = myproc();
// 關閉中斷,因為接下來會修改`stvec`指向uservec,此時如果發生中斷,程式會跳轉到使用者trap處理程式碼,容易出錯
intr_off();
// 設定STVEC指向uservec
uint64 trampoline_uservec = TRAMPOLINE + (uservec - trampoline);
w_stvec(trampoline_uservec);
// 將一些核心資訊儲存到trapframe中方便下次使用
p->trapframe->kernel_satp = r_satp();
p->trapframe->kernel_sp = p->kstack + PGSIZE;
p->trapframe->kernel_trap = (uint64)usertrap;
p->trapframe->kernel_hartid = r_tp();
unsigned long x = r_sstatus();
//SPP位和SPIE位都會影響sret指令的行為
// SPP為0表示下次執行sret的時候,返回user mode
// 而不是supervisor mode
x &= ~SSTATUS_SPP;
//SPIE位控制在執行完sret之後,是否開啟中斷。因為我們希望開啟中斷
//所以這裡將SPIE bit位設定為1
x |= SSTATUS_SPIE; // enable interrupts in user mode
w_sstatus(x);
// 將之前儲存的sepc值寫回
w_sepc(p->trapframe->epc);
// 根據user page table地址生成相應的SATP值
uint64 satp = MAKE_SATP(p->pagetable);
// 我們會在彙編程式碼trampoline.S\userret()中完成page table
// 的切換,切換隻能在trampoline中完成,因為只有trampoline中
// 的程式碼是同時在使用者和核心空間中對映的
// 求出trampoline.S\userret()的地址
uint64 trampoline_userret = TRAMPOLINE + (userret - trampoline);
// satp會作為第二個引數傳給userret()
((void (*)(uint64))trampoline_userret)(satp);
}
4.1.5 trampoline.S\userret()
userret:
# a0: user page table, for satp.
# switch to the user page table.
sfence.vma zero, zero
csrw satp, a0
sfence.vma zero, zero
# a0指向TRAPFRAME作為基地址
li a0, TRAPFRAME
# restore all but a0 from TRAPFRAME
ld ra, 40(a0)
ld sp, 48(a0)
ld gp, 56(a0)
ld tp, 64(a0)
...
# restore user a0
ld a0, 112(a0)
# sret功能:
# 1.程式切換回user mode
# 2.SEPC暫存器的數值會被複製到PC暫存器
# 3.重新開啟中斷
sret
系統呼叫執行結束