lab2中,我们将给xv6增加一些新的系统调用

System call tracing (moderate)

YOUR JOB
添加一个系统调用跟踪功能,该功能可能会在以后调试实验时有所帮助。创建一个新的trace系统调用来控制跟踪。它应该有一个参数,这个参数是一个整数“掩码”(mask),它的比特位指定要跟踪的系统调用。例如,要跟踪fork系统调用,程序调用trace(1 << SYS_fork),其中SYS_fork是kernel/syscall.h中的系统调用编号。如果在掩码中设置了系统调用的编号,则必须修改xv6内核,以便在每个系统调用即将返回时打印出一行。该行应该包含进程id、系统调用的名称和返回值;不需要打印系统调用参数。trace系统调用应启用对调用它的进程及其随后派生的任何子进程的跟踪,但不应影响其他进程。
hints

  • 在Makefile的UPROGS中添加$U/_trace
  • 运行make qemu,将看到编译器无法编译user/trace.c,因为系统调用的用户空间存根还不存在:将系统调用的原型添加到user/user.h,存根添加到user/usys.pl,以及将系统调用编号添加到kernel/syscall.h,Makefile调用perl脚本user/usys.pl,它生成实际的系统调用存根user/usys.S,这个文件中的汇编代码使用RISC-V的ecall指令转换到内核。一旦修复了编译问题(注:如果编译还未通过,尝试先make clean,再执行make qemu),就运行trace 32 grep hello README;但由于还没有在内核中实现系统调用,执行将失败。
  • 在kernel/sysproc.c中添加一个sys_trace()函数,它通过将参数保存到proc结构体(请参见kernel/proc.h)里的一个新变量中来实现新的系统调用。从用户空间检索系统调用参数的函数在kernel/syscall.c中,您可以在kernel/sysproc.c中看到它们的使用示例。
  • 修改fork()(请参阅kernel/proc.c)将跟踪掩码从父进程复制到子进程。
  • 修改kernel/syscall.c中的syscall()函数以打印跟踪输出。需要添加一个系统调用名称数组以建立索引。

在Makefile中添加用户程序trace.c

1
2
3
4
5
6
UPROGS=\
$U/_cat\
$U/_echo\
···
$U/_zombie\
$U/_trace\

在用户空间(user文件夹)中引入trace系统调用原型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// user/user.h
// system calls
int fork(void);
int exit(int) __attribute__((noreturn));
···
int uptime(void);
int trace(int);

// user/usys.pl
entry("fork");
entry("exit");
···
entry("uptime");
entry("trace");

在内核空间中添加系统调用编号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// kernel/syscall.h
// System call numbers
#define SYS_fork 1
#define SYS_exit 2
···
#define SYS_close 21
#define SYS_trace 22

// kernel/syscall.c
extern uint64 sys_trace(void);

static uint64 (*syscalls[])(void) = {
[SYS_fork] sys_fork,
[SYS_exit] sys_exit,
···
[SYS_close] sys_close,
[SYS_trace] sys_trace,
};

添加sys_trace()函数

这里需要将解析的参数保存到process之中,所以需要在proc结构体中添加新变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// kernel/sysproc.c
uint64 sys_trace(void) {
int n;

if (argint(0, &n) < 0) {
return 1;
}
myproc()->trace_code = n;
return 0;
}

// kernel/proc.h
// Per-process state
struct proc {
···
// these are private to the process, so p->lock need not be held.
uint64 kstack; // Virtual address of kernel stack
uint64 sz; // Size of process memory (bytes)
···
char name[16]; // Process name (debugging)
int trace_code;
};

这里用到了kernel/syscall.c的argint函数用来解析参数

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
static uint64 argraw(int n) {
struct proc *p = myproc();
switch (n) {
case 0:
return p->trapframe->a0;
case 1:
return p->trapframe->a1;
case 2:
return p->trapframe->a2;
case 3:
return p->trapframe->a3;
case 4:
return p->trapframe->a4;
case 5:
return p->trapframe->a5;
}
panic("argraw");
return -1;
}

// Fetch the nth 32-bit system call argument.
int argint(int n, int *ip) {
*ip = argraw(n);
return 0;
}

int argint(int n, int *ip) : 用于获取第 n 个 系统调用参数
调用 argraw() 函数获取参数值,然后通过指针 ip 把值写入到调用者提供的内存位置。

static uint64 argraw(int n) : 接受一个整型参数 n 作为输入,并返回一个 64 位无符号整型 (uint64) 的结果
函数内部首先使用 myproc() 函数获取当前进程的结构体 (struct proc *p)
然后根据输入参数 n 的值,返回进程陷阱帧 (p->trapframe) 中相应的参数值(p->trapframe->a0, p->trapframe->a1, …)

通过argint()解析参数,赋值给proc中的trace_code

修改fork()

由于struct proc中增加了一个新的变量,当fork的时候我们也需要将这个变量传递到子进程中

1
2
3
4
5
6
// kernel/proc.c
int fork(void) {
···
np->trace_code = p->trace_code;
···
}

修改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
33
34
35
36
37
38
39
40
41
// kernel/syscall.c
static char *syscall_name[] = {
[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_code) {
printf("%d: syscall %s -> %d\n", p->pid, syscall_name[num], p->trapframe->a0);
}
} else {
printf("%d %s: unknown sys call %d\n", p->pid, p->name, num);
p->trapframe->a0 = -1;
}
}

Sysinfo (moderate)

YOUR JOB
在这个作业中,您将添加一个系统调用sysinfo,它收集有关正在运行的系统的信息。系统调用采用一个参数:一个指向struct sysinfo的指针(参见kernel/sysinfo.h)。内核应该填写这个结构的字段:freemem字段应该设置为空闲内存的字节数,nproc字段应该设置为state字段不为UNUSED的进程数。我们提供了一个测试程序sysinfotest;如果输出“sysinfotest: OK”则通过。
hints

  • 在Makefile的UPROGS中添加$U/_sysinfotest
  • 当运行make qemu时,user/sysinfotest.c将会编译失败,遵循和上一个作业一样的步骤添加sysinfo系统调用。要在user/user.h中声明sysinfo()的原型,需要预先声明struct sysinfo的存在:
    1
    2
    3
    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中添加一个函数

准备工作

参考前一个task,在user和kernel中添加sysinfo系统调用

1
2
3
4
5
// user/sysinfo.h
struct sysinfo {
uint64 freemem; // amount of free memory (bytes)
uint64 nproc; // number of process
};

两个任务:进程数/空闲内存

进程数

在kernel/proc.c中对所有proc进行遍历,如果proc->state不为UNUSED状态,则计数加1

1
2
3
4
5
6
7
8
9
10
// kernel/proc.c
void free_proc_num(uint64 *ans) {
struct proc *p;
*ans = 0;
for (p = proc; p < &proc[NPROC]; p++) {
if (p->state != UNUSED) {
(*ans)++;
}
}
}

空闲内存

在访问keme.freelist时,为了保护现场安全需给其上锁,返回memory byte,每次+=PGSIZE

1
2
3
4
5
6
7
8
9
10
11
12
// kernel/kalloc.c
void free_mem(uint64 *ans) {
*ans = 0;
struct run *p = kmem.freelist;

acquire(&kmem.lock);
while (p) {
*ans += PGSIZE;
p = p->next;
}
release(&kmem.lock);
}

添加新定义的函数

在kernel/defs.h中加入刚刚写的两个函数

sys_sysinfo()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// kernel/sysproc.c
struct sysinfo {
uint64 freemem; // amount of free memory (bytes)
uint64 nproc; // number of process
};

uint64 sys_sysinfo(void) {
struct sysinfo info;
free_mem(&info.freemem);
free_proc_num(&info.nproc);

uint64 dstaddr;
argaddr(0, &dstaddr);

// 从内核空间拷贝数据到用户空间
if (copyout(myproc()->pagetable, dstaddr, (char *)&info, sizeof info) < 0) {
return -1;
}

return 0;
}