本文记录了 Lab: system calls 的实验过程

Lab: system calls

进入 xv6-labs-2020 目录, 执行

1
2
3
git fetch
git checkout syscall
make clean

System call tracing (moderate)

实验描述

实现一个 trace(int mask) 的系统调用。它的参数为 maskmask 指定了哪些系统调用会被打印。例如,为了跟踪 fork 系统调用,程序可以调用 trace(1 << SYS_fork) (其中,SYS_fork 定义在 kernel/syscall.h 中,表示 fork 系统调用的编号)。修改 xv6 kernel 代码,使得如果某一个系统调用的编号在 mask 中对应的位为 1 时,在系统调用返回前,将该信息打印出来。每一行应该包含进程的 id,系统调用的名字以及返回值。

提示

根据 Lab 的教程,仔细阅读下面六个文件中相关的代码

  • The user-space code for systems calls is in user/user.h and user/usys.pl
  • The kernel-space code is kernel/syscall.h, kernel/syscall.c
  • The process-related code is kernel/proc.h and kernel/proc.c

然后在根据提示修改对应文件中的代码。

实验步骤

  1. 打开目录中的 Makefile 文件,在 UPROGS 中添加:$U/_trace

  2. 对 trace 进行定义。首先在 user/user.h 文件中添加 trace() 系统调用的声明

1
2
// system calls
int trace(int);

user/usys.pl 中添加 trace() 的入口

1
entry("trace");

kernel/syscall.h 中给它增加一个编号

1
#define SYS_trace  22
  1. kernel/sysproc.c 文件中添加函数 sys_trace(),它的功能是读取 trace() 传进来的参数 mask,然后保存到 proc 结构体(定义在 kernel/proc.h)中的新增的变量中。
1
2
3
4
5
struct proc {
...
// trace system call's parameter
int mask;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// kernel/sysproc.c
uint64
sys_trace(void)
{
// the parameter of trace
int mask;

if(argint(0, &mask) < 0)
return -1;

myproc()->mask = mask;

return 0;
}
  1. fork() 函数中(定义在 kernel/proc.c),将父进程的 mask 赋给子进程。
1
np->mask = p->mask;
  1. 修改 kernel/syscall.c 文件中的 syscall() 函数,打印需要输出的信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
...
extern uint64 sys_trace(void);

static uint64 (*syscalls[])(void) = {
...
[SYS_trace] sys_trace,
};

// 为了打印方便,设置一个系统调用名的字符串数组
const char *syscall_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]();
//process execute the system call, by mask, print system call name
if (p->mask & (1 << num)) {
printf("%d: syscall %s -> %d\n", p->pid, syscall_name[num - 1], p->trapframe->a0);
}
} else {
printf("%d %s: unknown sys call %d\n",
p->pid, p->name, num);
p->trapframe->a0 = -1;
}
}

总结

由于 mask 没理解清楚,导致打印了很多没用的信息。比如,mask = 2 = $(10)_2$ = (1 << 1),说明我们只需要跟踪 fork 这个系统调用(fork 系统调用的编号为 1)。mask = 32 = $(100000)_2$ = (1 << 5),说明我们只需要跟踪 read 这个系统调用。当 mask = 2147583647 = $2^{31}-1$,即 31 个 1,所以我们跟踪所有进程。即:mask 的二进制数从左向右数(最左边为第 0 位),第 n 位为 1 时,说明我们要打印第 n 个系统调用的信息。

Sysino

实验描述

该实验需要我们实现一个 sysinfo 的系统调用,它将收集系统的信息。它接收一个指向 struct sysinfokernel/sysinfo.h)的指针。然后会将当前系统的剩余内存的字节数保存在字段 freemem 中,设置 nproc 为进程状态不为 UNUSED 的个数。

实验步骤

  1. 打开目录中的 Makefile 文件,在 UPROGS 中添加:$U/_sysinfo

  2. sysinfo 进行定义。首先在 user/user.h 文件中添加 sysinfo 系统调用以及 sysinfo 结构体的声明

1
2
struct sysinfo;
int sysinfo(struct sysinfo *);

user/usys.pl 中添加 sysinfo() 的入口

1
entry("sysinfo");

kernel/syscall.h 中给它增加一个编号

1
#define SYS_sysinfo  23
  1. kernel/defs.h 声明函数的原型(将下面函数声明添加到文件中的对应位置)
1
2
3
4
5
// kalloc.c
int kgetfreemem(void);

// proc.c
int getNproc(void);
  1. 函数的实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 在 kalloc.c 文件中添加函数的实现

int
kgetfreemem(void)
{
int count = 0;

struct run *r = kmem.freelist;

while (r)
{
count++;
r = r->next;
}
return count * PGSIZE;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 在 proc.c 文件中添加函数的实现

int
getNproc(void)
{
int cnt = 0;
struct proc *p;

for(p = proc; p < &proc[NPROC]; p++) {
acquire(&p->lock);
if(p->state != UNUSED) {
cnt++;
}
release(&p->lock);
}
return cnt;
}
  1. kernel/sysfile.c 中实现 sys_sysinfo()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include "sysinfo.h"

uint64
sys_sysinfo(void)
{
uint64 si; // user pointer to struct sysinfo

if (argaddr(0, &si) < 0)
return -1;

struct proc *p = myproc();
struct sysinfo s;

s.freemem = kgetfreemem();
s.nproc = getNproc();

if(copyout(p->pagetable, si, (char *)&s, sizeof(s)) < 0)
return -1;
return 0;
}
  1. 最后在 kernel/syscall.c 文件中添加
1
2
3
4
5
6
7
...
extern uint64 sys_sysinfo(void);

static uint64 (*syscalls[])(void) = {
...
[SYS_sysinfo] sys_sysinfo,
};

总结

一定要仔细阅读、理解提示中让你看的源码文件

测试

1
./grade-lab-syscall trace
1
./grade-lab-syscall sysinfotest
1
make grade