iT邦幫忙

2021 iThome 鐵人賽

DAY 22
0
自我挑戰組

C 語言筆記系列 第 22

[C 語言筆記--Day22] 6.S081 Lab syscall: 在 xv6 中新增一個 System Call

關於 xv6 的環境架設,可以參考我之前寫的這篇文章

6.S082 課程連結(我這裡用的是 2021 的版本)

這篇文章是要寫課程當中的 Lab syscall

大綱

1. 開始寫題目前應該要先做的幾件事

  • 1.1 觀看課程影片
  • 1.2 閱讀 xv6 book
  • 1.3 閱讀一些程式碼

2. 題目叫我們閱讀的程式碼

  • 2.1 user-space code for systems calls: user/user.h, user/usys.pl
  • 2.2 kernel-space code: kernel/syscall.h, kernel/syscall.c
  • 2.3 process-related code: kernel/proc.h, kernel/proc.c

3. 程式實作

  • 3.1 讓程式碼可以成功編譯
  • 3.2 實作 system call
  • 3.3 其他的小修改

4. 參考資料

1. 開始寫題目前應該要先做的幾件事

1.1 觀看課程影片

課程進度表中 的 9/15 那天有個 video

先把他看完對於寫 lab 會有幫助的

1.2 閱讀 xv6 book

如果你看的是 2020 的版本,可以直接點這個連結

但如果你看的是 2021 的版本了話,要點選這個 github 頁面 下載程式碼並且自己 make 出來才行

我個人覺得 xv6 book 寫的不是那麼平易近人,很多時候他假設了讀者有一定的基礎知識,

所以我是建議在書中看到了什麼不懂的名詞,就先去搞懂後再回來看會比較好,

像是 page table, virtual memory, risc-v 的架構,一些 C 語言語法等等,

雖然這樣感好像很花時間,但反正在看其他的東西本身也是一種學習,

Lab syscall 要我們先讀 chapter 2, 4.3, 4.4,

就先把他們讀完再繼續吧,讀的時候也要搭配 source code 會比要好懂

2. 題目叫我們閱讀的程式碼

2.1 user-space code for systems calls: 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.csyscall()
    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

2.2 kernel-space code: 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 寫的很清楚

2.3 process-related code: kernel/proc.h, kernel/proc.c

在 kernel 中,紀錄著各個 process 的資訊,而他使用的資料結構就是 kernel/proc.hstruct proc

這個 struct 值得多加注意一下

3. 程式實作

依照 Lab system callSome hints 的指示,一步一步的做下去就好

而這個 lab 已經提供了 user program user/trace.c 但是這個 program 呼叫到的 system call trace(int) 還沒有被實作出來

我們的目的就是要把他實作出來才行

3.1 讓程式碼可以成功編譯

  • 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 上的問題

3.2 實作 system call

  • kernel/proc.h 中,增加一個 variable
struct 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,
};

3.3 其他的小修改

  • kernel/proc.c 中的 fork() fork 出新的 process 時,也要把 trace_mask 也複製過去才行
int
fork(void)
{
  ...
  // copy trace_mask
  np->trace_mask = p->trace_mask;
  ...
}
  • kernel/syscall.csyscall() 要在 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;
  }
}

4. 參考資料


上一篇
[C 語言筆記--Day20] 解題紀錄 10190 Divide, But Not Quite Conquer!
下一篇
[C 語言筆記--Day22] warning: ISO C++ forbids converting a string constant to ‘char*’ [-Wwrite-strings]
系列文
C 語言筆記30

尚未有邦友留言

立即登入留言