MIT6.S081 ---- Lab System calls
Lab System calls
物理資源抽象設計(檔案系統管理,記憶體使用,CPU切換,管道互動);指令執行的三種狀態(M,S,U),系統呼叫;巨集核心和微核心;程序機制(S/U mode,地址空間,時間片),程序目的(增強隔離性),程序的地址空間和狀態項,程序的設計思想;xv6的啟動概述;安全相關。
學習系統呼叫的原理和實現。
Lab準備
calling convention
RISC-V的calling約定一般是在暫存器間傳參,最多有8個整數暫存器(a0-a7)和8個浮點暫存器(fa0-fa7)。
當在整數暫存器中傳參時,引數儲存在對齊的even-odd暫存器對中,even暫存器儲存最低有效位。RV32中,如函式void foo(int, long long)
struct中沒有通過引數暫存器傳遞的部分在棧上傳遞,棧指標sp指向第一個沒有在引數暫存器中傳遞的引數。
返回值儲存在暫存器a0和a1中或者暫存器fa0和fa1中。只有當浮點值是單獨的浮點值或者只有一兩個浮點陣列成的struct結構才可以通過浮點暫存器返回。其他兩個指標字大小的返回值用a0和a1返回。
更大的返回值在記憶體中傳遞,由呼叫者分配記憶體空間,將第一個引數的指標傳給被呼叫者。
系統呼叫的過程
initcode.S將exec的引數放在暫存器a0,a1中,將系統呼叫號放在暫存器a7中,系統呼叫號可以索引syscalls
ecall
(對執行環境發request,在X(U,S,M)-mode下執行ecall,將會產生environmen-call-from-X-mode異常)指令trap進核心,執行uservec
(執行從使用者空間產生的traps),執行usertrap
(處理來自使用者空間的中斷、異常、系統呼叫),執行syscall
。
syscall
(kernel/syscall.c:133)從trapframe->a7
取出系統呼叫號,索引syscalls
。呼叫系統呼叫實現sys_exec
,將返回值存在trapframe->a0
,這也是使用者空間呼叫的exec()
syscall
輸出error並返回-1。
系統呼叫的引數
核心中的系統呼叫實現需要找到使用者程式碼傳遞的引數。因為使用者程式碼呼叫的是封裝好的函式,RISC-V calling convention約定將引數放在暫存器中。核心的trap程式碼將使用者暫存器的內容存在程序的trap frame中,供核心使用。核心函式argint
,argaddr
,argfd
取出放在trap frame中的指定系統呼叫的引數,它們都是呼叫argraw
(kernel/syscall.c)。
一些系統呼叫使用指標作為傳參,核心必須使用這些指標讀寫使用者記憶體。例如,exec
系統呼叫傳給核心指向儲存在使用者空間的引數陣列的指標陣列。這些指標帶來兩個挑戰:
- 使用者程式碼可能有bugs或是惡意程式碼,或者傳給核心非法的指標,或者指標有意指向核心空間而非使用者空間。
- xv6核心頁表對映和使用者頁表對映不同,所以核心不能使用普通的指令從使用者提供的地址存取資料。
核心實現了對使用者提供的地址進行安全資料傳輸的函式。這裡下章具體學習,本章學會使用copyout
即可完成實驗。
System call tracing
實現一個系統呼叫,能列印指定的系統呼叫的資訊。
通過接收引數mask,確定指定的系統呼叫,在系統呼叫將返回時,打印出它的資訊(程序id,系統呼叫名稱,返回值)。子程序繼承這個mask,不影響其他程序。
在struct proc
中加入int tracemask
,用於確定需要trace哪些系統呼叫。這個屬性不需要加鎖。在allocproc
和freeproc
中對這個屬性進行初始化和清除。
在syscall
(kernel/syscall.c)中加入實現
if ((p->tracemask & (1 << num)) != 0) {
printf("%d: syscall %s -> %d\n", p->pid, syscallnames[num-1], p->trapframe->a0);
}
在(kernel/sysproc.c)中實現sys_trace
。
// Set mask that specify which system calls to trace.
uint64
sys_trace(void)
{
int tracemask;
if(argint(0, &tracemask) < 0)
return -1;
myproc()->tracemask = tracemask;
return 0;
}
其餘配置參照hints修改。
Sysinfo
實現一個系統呼叫:收集xv6的空閒記憶體大小和程序數量,並將值返回給使用者空間。
在(kernel/sysproc.c)中完成sysinfo系統呼叫的核心實現sys_sysinfo
。
// Get the number of bytes of free memory,
// and the number of processes whose state is not UNUSED.
uint64
sys_sysinfo(void)
{
uint64 uinfop; // user pointer to struct sysinfo
if(argaddr(0, &uinfop) < 0)
return -1;
return setsysinfo(uinfop);
}
在(kernel/proc.c)完成setsysinfo
和procnum
。分別實現將sysinfo資訊傳給使用者空間和統計程序數量。
// collect the nunmber of processes.
// Return the number of processes whose state is not UNUSED.
uint64
procnum(void)
{
struct proc *p;
uint64 nproc = 0;
for (p = proc; p < &proc[NPROC]; p++) {
acquire(&p->lock);
if (p->state != UNUSED) {
nproc++;
}
release(&p->lock);
}
return nproc;
}
// Set struct sysinfo and copy back to user space.
int
setsysinfo(uint64 addr)
{
struct proc *p = myproc();
struct sysinfo info;
info.freemem = kremainsize();
info.nproc = procnum();
if(copyout(p->pagetable, addr, (char *)&info, sizeof(info)) < 0)
return -1;
return 0;
}
在(kernel/kalloc.c)完成kremainsize
,實現空閒空閒記憶體的統計。
// collect the amount of free memory.
// Returns the number of bytes of free memory.
uint64
kremainsize(void)
{
struct run *r;
uint64 freepg = 0;
acquire(&kmem.lock);
r = kmem.freelist;
while (r) {
freepg++;
r = r->next;
}
release(&kmem.lock);
return freepg << 12;
}