關於 xv6 的環境架設,可以參考我之前寫的這篇文章
6.S082 課程連結(我這裡用的是 2021 的版本)
這篇文章是要寫課程當中的 Lab syscall
1. 開始寫題目前應該要先做的幾件事
2. 題目叫我們閱讀的程式碼
user/user.h
, user/usys.pl
kernel/syscall.h
, kernel/syscall.c
kernel/proc.h
, kernel/proc.c
3. 程式實作
4. 參考資料
先把他看完對於寫 lab 會有幫助的
如果你看的是 2020 的版本,可以直接點這個連結
但如果你看的是 2021 的版本了話,要點選這個 github 頁面 下載程式碼並且自己 make
出來才行
我個人覺得 xv6 book 寫的不是那麼平易近人,很多時候他假設了讀者有一定的基礎知識,
所以我是建議在書中看到了什麼不懂的名詞,就先去搞懂後再回來看會比較好,
像是 page table, virtual memory, risc-v 的架構,一些 C 語言語法等等,
雖然這樣感好像很花時間,但反正在看其他的東西本身也是一種學習,
在 Lab syscall 要我們先讀 chapter 2, 4.3, 4.4,
就先把他們讀完再繼續吧,讀的時候也要搭配 source code 會比要好懂
user/user.h
, user/usys.pl
user/user.h
,是個 system call 的 prototype
/*.........*/
// system calls
int fork(void);
int exit(int) __attribute__((noreturn));
int wait(int*);
int pipe(int*);
int write(int, const void*, int);
int read(int, void*, int);
int close(int);
int kill(int);
int exec(char*, char**);
int open(const char*, int);
int mknod(const char*, short, short);
int unlink(const char*);
int fstat(int fd, struct stat*);
int link(const char*, const char*);
int mkdir(const char*);
int chdir(const char*);
int dup(int);
int getpid(void);
char* sbrk(int);
int sleep(int);
int uptime(void);
/*.........*/
但就以 int chdir(const char*)
為例,在 kernel 中,他並不會真的有
int chdir(const char*)
{
//***........***//
}
這種實作方式
而是在 make qemu
之後,Makefile
會執行 user/usys.pl
這個腳本程式,
他會製造出 user/usys.S
這個 risc-v 組語:
#include "kernel/syscall.h"
.global fork
fork:
li a7, SYS_fork
ecall
ret
.global exit
exit:
li a7, SYS_exit
ecall
ret
...
也就是在 user program 中,叫到了 fork()
這個 function 之後,他會跳到 fork:
這個 flag(symbol) 這裡來繼續執行
如果 user program 呼叫了 fork()
這個 function 會執行以下兩件事:
li
(load immediate):把 fork 的編號 SYS_fork
(定義在 kernel/syscall.h
) 放進 register a7
裡ecall
:從 user mode 進入到 supervisor mode (實際上就是改變了 CPU chip 裡的一個 flag)並且進入到了 kernel/syscall.c
的 syscall()
syscall()
會根據 register a7
的 system call 編號來幫你把 program counter 指向 system call 真的實做的 function(在 kernel/sysfile.c
或是 kernel/sysproc.c
等等)
這麼做的原因在於,system call 是個權限較高的行為,所以處理起來要比較小心才行
user program 只能把呼叫的 system call 編號放在 register a7
然後由 syscall() 來決定這個 user program 是否有資格執行這個 system call
kernel/syscall.h
, kernel/syscall.c
像前面有提到的,kernel/syscall.h
定義著 system call 的編號:
// System call numbers
#define SYS_fork 1
#define SYS_exit 2
#define SYS_wait 3
#define SYS_pipe 4
#define SYS_read 5
#define SYS_kill 6
#define SYS_exec 7
#define SYS_fstat 8
#define SYS_chdir 9
#define SYS_dup 10
#define SYS_getpid 11
#define SYS_sbrk 12
#define SYS_sleep 13
#define SYS_uptime 14
#define SYS_open 15
#define SYS_write 16
#define SYS_mknod 17
#define SYS_unlink 18
#define SYS_link 19
#define SYS_mkdir 20
#define SYS_close 21
kernel/syscall.c
則定義著一些執行 system call 所需要用的 function ,
例如如何拿取 user program 傳來的參數等等,這些在 xv6 book 寫的很清楚
kernel/proc.h
, kernel/proc.c
在 kernel 中,紀錄著各個 process 的資訊,而他使用的資料結構就是 kernel/proc.h
的 struct proc
這個 struct
值得多加注意一下
依照 Lab system call 的 Some hints 的指示,一步一步的做下去就好
而這個 lab 已經提供了 user program user/trace.c
但是這個 program 呼叫到的 system call trace(int)
還沒有被實作出來
我們的目的就是要把他實作出來才行
Makefile
中,加入 $U/_trace
user/user.h
:// user/user.h
...
int trace(int);
...
user/usys.pl
:...
entry("trace");
kernel/syscall.h
...
#define SYS_trace 22
到了這裡,已經可以用 make qemu
compile 成功了,
只不過 trace
這個 system call 還是還沒實作出來,
目前只解決了以 user program 而言的 link 上的問題
kernel/proc.h
中,增加一個 variablestruct proc {
...
uint trace_mask;
};
kernel/sysproc.c
實作出 sys_trace()
...
uint64
sys_trace(void)
{
int n;
if(argint(0, &n) < 0)
return -1;
struct proc *p = myproc();
p->trace_mask = n;
return 0;
}
其實就只是把 proc
中的 trace_mask
給設為 user program 傳過來的參數而已
但業就是因為他改變了 kernel 中的資訊,所以需要用更高等級的權限(supervisor mode)才可以
kernel/syscall.c
加入extern uint64 sys_trace(void);
static uint64 (*syscalls[])(void) = {
...
[SYS_trace] sys_trace,
};
kernel/proc.c
中的 fork()
fork 出新的 process 時,也要把 trace_mask
也複製過去才行int
fork(void)
{
...
// copy trace_mask
np->trace_mask = p->trace_mask;
...
}
kernel/syscall.c
的 syscall()
要在 system call return 時,判斷要不要 print 出資訊static char *syscall_names[] = {
[SYS_fork] "fork",
[SYS_exit] "exit",
[SYS_wait] "wait",
[SYS_pipe] "pipe",
[SYS_read] "read",
[SYS_kill] "kill",
[SYS_exec] "exec",
[SYS_fstat] "fstat",
[SYS_chdir] "chdir",
[SYS_dup] "dup",
[SYS_getpid] "getpid",
[SYS_sbrk] "sbrk",
[SYS_sleep] "sleep",
[SYS_uptime] "uptime",
[SYS_open] "open",
[SYS_write] "write",
[SYS_mknod] "mknod",
[SYS_unlink] "unlink",
[SYS_link] "link",
[SYS_mkdir] "mkdir",
[SYS_close] "close",
[SYS_trace] "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 ((1 << num) & p->trace_mask)
printf("%d: syscall %s -> %d\n", p->pid, syscall_names[num], p->trapframe->a0);
} else {
printf("%d %s: unknown sys call %d\n",
p->pid, p->name, num);
p->trapframe->a0 = -1;
}
}