Lab2: system calls 預備知識 執行一次系統調用的流程: USER MODE step1:系統調用聲明 user/user.h:系統調用函數(如 int fork(void)) step2:ecall 進入內核態 user/usys.S(該文件由 user/usys.pl 生成,後續 ...
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 語言還需要精進