Lab2: system calls
預備知識
執行一次系統呼叫的流程:
USER MODE
step1:系統呼叫宣告
user/user.h
:系統呼叫函式(如int fork(void)
)
step2:ecall 進入核心態
user/usys.S
(該檔案由user/usys.pl
生成,後續新增函式可以在這裡新增):執行如下命令
.global fork
fork:
li a7, SYS_fork
ecall
ret
- 將系統呼叫的編號(在
kernel/syscall.h
中定義)寫入a7
暫存器 - 從
ecall
進入中斷處理函式
KERNEL MODE
step3:儲存資料並跳轉到中斷判斷函式
kernel/trampoline.S
:在uservec
中儲存暫存器、切換到核心棧、切換棧指標SP等,最後跳轉到核心指定的中斷判斷函式usertrap
(在kernel/trap.c
中)- 每一個程序都對應一個狀態結構體
proc
(在kernel/proc.h
中定義這個結構體),將資料儲存在這裡
- 每一個程序都對應一個狀態結構體
step4:中斷判斷函式
kernel/trap.c
:中斷判斷函式usertrap
用於處理來自使用者態的中斷、異常或系統呼叫,在此處判斷是否是系統呼叫,如果是,則執行響應函式syscall
step5:執行對應的系統呼叫
kernel/syscall.c
:響應函式syscall
用於對號入座,根據給出的syscalls
表(將系統呼叫編號與執行函式相對應)獲取系統呼叫型別(系統呼叫編號從a7
暫存器中讀取),執行相應的函式(進入kernel/sysproc.c
中);系統呼叫的返回值傳遞給了a0
,後續會回傳給使用者態
step6:系統呼叫函式
kernel/sysproc.c
:儲存著系統呼叫的執行函式,如果系統呼叫有引數,需要用argraw
(讀取引數本質上是從記憶體中恢復,見kernel/syscall.c
)取出儲存的暫存器資料,然後函式最終都將呼叫相對應的執行函式(在kernel/proc.c
中)
step7:系統呼叫核心功能
kernel/proc.c
:這裡的函式用於執行核心功能,如建立程序等
新增系統呼叫需要考慮的地方
-
在
user/user.h
中新增系統呼叫的函式宣告,並在user
中建立相應的系統呼叫(*.c
檔案)(和lab1類似) -
在
user/usys.pl
中新增系統呼叫項 -
在
kernel/syscall.h
新增系統呼叫的編號 -
在
kernel/syscall.c
中的syscalls
表中新增對映關係,指出需要執行的函式 -
在
kernel/sysproc.c
和kernel/proc.c
中實現系統呼叫的執行函式
Part1:System call tracing
實現功能
-
新增一個系統呼叫的
trace
功能,在命令前輸入trace <mask>
,能夠列印出該命令使用的系統呼叫 -
mask = 1 << SYS_name
為一個整數,它能夠指定跟蹤哪個系統呼叫,SYS_name
是來自kernel/syscall.h
的一個系統呼叫號,mask
可以等於1 << SYS_name | 1 << SYS_other_name
-
如果在輸入命令時使用
trace <mask>
,則在使用指定系統呼叫後應該返回一行包括程序id、系統呼叫名稱和返回值
的列印資訊 -
trace
要求能夠跟蹤程序以及程序派生出的子程序,且不影響其他程序
實驗提示
-
在
user/user.h
、user/usys.pl
、kernel/syscall.h
新增系統呼叫以及編號 -
在
kernel/sysproc.c
新增一個sys_trace
函式實現新的系統呼叫,並在狀態結構體proc
中插入一個新的變數(對該程序進行跟蹤的mask
掩碼),系統呼叫的執行函式參考kernel/sysproc.c
-
此外需要對
kernel/proc.c
的fork
函式進行修改,因為呼叫了trace
系統呼叫,會在當前程序狀態proc
(在kernel/proc.h
中)中修改mask
掩碼的設定,同時每次fork
時也需要對相關的子程序同步相關的設定 -
需要修改
kernel/syscall.c
下的syscall
使其列印追蹤資訊;為了列印系統呼叫名稱,需要額外建立字串陣列
實驗程式碼
-
在
user/user.h
中新增int trace(int);
的宣告(trace接受一個整型的引數mask
) -
在
user/usys.pl
中新增entry("trace");
,這是進入核心態的入口 -
在
Makefile
中的UPROGS
新增_trace
-
進入核心態,在
kernel/syscall.h
新增系統呼叫的編號#define SYS_trace 22
-
在
kernel/syscall.c
中新增系統呼叫的對映extern uint64 sys_trace(void);
、[SYS_trace] sys_trace
-
在
kernel/proc.h
中的proc
中新增int mask
-
在
kernel/sysproc.c
中新增進入系統呼叫的函式
uint64
sys_trace(void){ // 參考sys_wait函式
uint64 p;
if(argaddr(0, &p) < 0) // 獲取trace的引數(只有一個)
return -1;
return trace(p); // 系統呼叫的執行函式
}
- 在
kernel/proc.c
實現trace
的系統呼叫執行函式(僅僅是進行一個掩碼的賦值)
int
trace(int mask){
struct proc *p = myproc();
p->mask = mask; // 將掩碼賦值給結構體的mask
return 0;
}
- 在
kernel/defs.h
(這個裡面包含了kernel
中常用函式的原型宣告)中加入函式的宣告int trace(int)
- 在
kernel/proc.c
中的fork
函式中加一行程式碼np->mask = p->mask;
,將父程序的 mask 賦值給子程序的 mask - 在
kernel/syscall.c
中對syscall
進行修改,列印追蹤資訊;並且為了列印系統呼叫的名稱,建立一個字串陣列
char
*sys_name[] = {
"", "fork", "exit", "wait", "pipe", "read", "kill", "exec",
"fstat", "chdir", "dup", "getpid", "sbrk", "sleep", "uptime", "open",
"write", "mknod", "unlink", "link", "mkdir", "close", "trace"
};
void
syscall(void)
{
int num;
struct proc *p = myproc();
num = p->trapframe->a7;
if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
p->trapframe->a0 = syscalls[num](); // 返回值
if(p->mask >> num & 1){ // 判斷是否有mask輸入
// 列印程序id、系統呼叫的名稱和返回值
printf("%d: syscall %s -> %d", p->pid, sys_name[num], p->trapframe->a0); // 這裡注意使用的prinrf是因為xv6的kernel中專門定義了一個printf的函式,在Linux核心中只能使用prinrk列印
}
} else {
printf("%d %s: unknown sys call %d\n",
p->pid, p->name, num);
p->trapframe->a0 = -1;
}
}
p->mask >> num & 1
:p->mask 為 1<<SYS_read,num 為從 a7 暫存器中讀出來的 SYS_read,p->mask >> num 能夠將 mask 還原為 SYS_read,如果沒有 mask,則不列印
實驗感悟
- 在閱讀原始碼的過程中,看到有些地方涉及到組合語言,之後要把這塊給學習一下,要不然很影響閱讀體驗
- 雖然按照系統呼叫的步驟能把整個流程走下來,但是每個函式以及它們之間的關係實際上還沒有特別清楚,需要再進行梳理(將每個函式的作用都搞清楚)
- 在進行
proc
修改的時候,要考慮到fork
函式的子程序是否需要複製引數
Part2: Sysinfo
實現功能
- 新增一個系統呼叫
sysinfo
,透過系統呼叫將收集正在執行的程式的資訊 - 在這個系統呼叫中將傳入一個結構體指標
sysinfo
,結構體定義在kernel/sysinfo.h
中,其中freemem
應該設定為空閒記憶體的位元組數,nproc
應該設定為程序狀態不為UNUSED
的程序數
實驗提示
- 在
user/user.h
中宣告sysinfo()
的原型,你需要預先宣告結構體sysinfo
的存在:struct sysinfo;
、int sysinfo(struct sysinfo *);
- sysinfo 需要將
struct sysinfo
複製回使用者空間;參考sys_fstat() (kernel/sysfile.c)
和filestat() (kernel/file.c)
學習使用copyout()
- 要收集空閒記憶體量,可以在
kernel/kalloc.c
中新增一個函式 - 要收集程序數,請在
kernel/proc.c
中新增一個函式
實驗程式碼
- 在
user/user.h
新增sysinfo
結構體和函式的宣告struct sysinfo;
、int sysinfo(struct sysinfo *);
- 分別在
user/usys.pl
、Makefile
、kernel/syscall.h
、kernel/syscall.c
執行與上一個實驗一樣的步驟 - 在
kernel/kalloc.c
中實現空閒記憶體大小的查詢
uint64
getfreemen(void){ // 獲取空閒記憶體數量
struct run *rp;
uint64 result = 0;
acquire(&kmem.lock); // 考慮到併發問題,上鎖
rp = kmem.freelist;
while(rp){
result += 1;
rp = rp->next;
}
release(&kmem.lock);
return result * PGSIZE; //一個記憶體頁的大小為PGSIZE(4096)
}
- 在
kernel/proc.c
中實現程序數的統計
int
getproc(void){
struct proc *p;
int result = 0;
for(p = proc; p < &proc[NPROC]; p++){
acquire(&p->lock); //上鎖
if(p->state != UNUSED){
result += 1;
}
release(&p->lock);
}
return result;
}
上述兩個步驟都需要在
kernel/defs.h
中新增函式宣告
- 在
kernel/sysproc.c
中實現執行函式sys_sysinfo
的功能,記得新增#include "sysinfo.h"
來使用sysinfo
結構體
uint64
sys_sysinfo(void){
struct sysinfo info;
struct proc *p;
uint64 addr;
if(argaddr(0, &addr) < 0) // 獲取系統的指標引數
return -1;
p = myproc();
info.freemem = getfreemen();
info.nproc = getproc();
// 從核心copy到使用者
if(copyout(p->pagetable, addr, (char *)&info, sizeof(info)) < 0) //參考sys_fstat(在kernel/sysfile.c中)
return -1;
return 0;
}
實驗感悟
- 這個實驗實現的記憶體大小和程序數的統計函式需要了解了 kalloc.c 和 proc.c 的函式後才能寫出來,我兩個檔案看的還不太完全,之後需要再看看
- 這個實驗中也使用了指標,這一塊使用還不太熟練,C 語言還需要精進