[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. 参考资料


<<:  [Day18] Null byte Injection

>>:  Day 20 AWS云端实作起手式最後一弹 整体架构回顾

Day 15:如何解决 PowerShell 无法使用 Angular 指令的问题?

学习 Angular 的过程中,遇到了一些教学资源不见得会遇到的问题,还真是家常便饭,而且你个人电脑...

Day 28:开始来学资料系结:使用目前所学,来个简单实作吧!(二)

前一篇,我们完成了需求一: 当使用者在关键字搜寻这个 input 输入文字时,要在输入框的正下方显示...

[从0到1] C#小乳牛 练成基础程序逻辑 Day 27 - File I/O 逐字识别码@ 时间日期 乱数

读写档案 | 跳脱字元\ | 逐字识别码@ | DateTime | Random ...

什麽是 Rack?

本文章同步发布於 我的部落格 什麽是 Rack ? Rack 是 Ruby 所有的网路框架背後最底层...

修改 DOM 元素样式

在网页的互动效果中,常常是当使用者符合了某个条件时,画面上的元素产生令人惊喜的变化,这些变化可简单可...