Lab 2: System Calls
Lab 2: System Calls
在上一个实验中,你使用系统调用编写了一些实用程序。在这个实验中,你将向 xv6 添加一些新的系统调用,这将帮助你理解它们的工作原理,并让你接触到 xv6 内核的一些内部机制。在后续的实验中,你将添加更多的系统调用。
注意
开始编码之前,请阅读 xv6 书籍的第 2 章,以及第 4 章的第 4.3 节和第 4.4 节,并阅读相关的源文件:
- 用户空间的系统调用代码 在 user/user.h 和 user/usys.pl 中。
- 内核空间的系统调用代码 在 kernel/syscall.h 和 kernel/syscall.c 中。
- 与进程相关的代码 在 kernel/proc.h 和 kernel/proc.c 中。
开始实验,切换到 syscall
分支:
$ git fetch
$ git checkout syscall
$ make clean
如果你运行 make grade
,你会看到评分脚本无法执行 trace
和 sysinfotest
。你的任务是添加必要的系统调用和存根,使它们能够正常工作
System call tracing (moderate)
重要
在这个任务中,你将添加一个系统调用跟踪功能,这可能会在你调试后续实验时有所帮助。你将创建一个新的 trace
系统调用,用于控制跟踪。它应该接受一个参数,一个整数“掩码”,其位指定要跟踪哪些系统调用。例如,要跟踪 fork
系统调用,程序调用 trace(1 << SYS_fork)
,其中 SYS_fork
是 kernel/syscall.h
中的系统调用号。你需要修改 xv6 内核,以便在每个系统调用即将返回时,如果系统调用号在掩码中设置,则打印一行。该行应包含进程 ID、系统调用的名称和返回值;你不需要打印系统调用参数。trace
系统调用应为调用它的进程及其随后派生的任何子进程启用跟踪,但不应影响其他进程。
我们提供了一个用户级程序 trace
,它可以在启用跟踪的情况下运行另一个程序(参见 user/trace.c
)。当你完成后,你应该看到类似以下的输出:
$ trace 32 grep hello README
3: syscall read -> 1023
3: syscall read -> 966
3: syscall read -> 70
3: syscall read -> 0
$
$ trace 2147483647 grep hello README
4: syscall trace -> 0
4: syscall exec -> 3
4: syscall open -> 3
4: syscall read -> 1023
4: syscall read -> 966
4: syscall read -> 70
4: syscall read -> 0
4: syscall close -> 0
$
$ grep hello README
$
$ trace 2 usertests forkforkfork
usertests starting
test forkforkfork: 407: syscall fork -> 408
408: syscall fork -> 409
409: syscall fork -> 410
410: syscall fork -> 411
409: syscall fork -> 412
410: syscall fork -> 413
409: syscall fork -> 414
411: syscall fork -> 415
...
$
在第一个示例中,trace
调用 grep
仅跟踪 read
系统调用。32
是 1 << SYS_read
。在第二个示例中,trace
运行 grep
时跟踪所有系统调用;2147483647
设置了所有 31 个低位。在第三个示例中,程序未被跟踪,因此没有打印跟踪输出。在第四个示例中,usertests
中 forkforkfork
测试的所有后代进程的 fork
系统调用正在被跟踪。如果你的程序的行为如上所示(尽管进程 ID 可能不同),则你的解决方案是正确的。
一些提示:
- 将
$U/_trace
添加到Makefile
中的UPROGS
。 - 运行
make qemu
,你会看到编译器无法编译user/trace.c
,因为用户空间的系统调用存根还不存在:向user/user.h
添加系统调用的原型,向user/usys.pl
添加存根,并向kernel/syscall.h
添加系统调用号。Makefile
调用user/usys.pl
脚本,该脚本生成user/usys.S
,即实际的系统调用存根,它们使用 RISC-V 的ecall
指令过渡到内核。一旦你修复了编译问题,运行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()
函数以打印跟踪输出。你需要添加一个系统调用名称数组来进行索引。
实验代码
usys.pl/user
entry("uptime");
// ======add code begin========
entry("trace");
//===========end===============
user.h/user
int sleep(int);
int uptime(void);
// ======add code begin========
int trace(int);
//===========end===============
syscall.h/kernel
#define SYS_mkdir 20
#define SYS_close 21
// ======add code begin========
#define SYS_trace 22
//===========end===============
proc.h/kernel
int xstate; // Exit status to be returned to parent's wait
int pid; // Process ID
// ======add code begin========
int trace_code; // The Trace System Call Number
//===========end===============
// wait_lock must be held when using this:
struct proc *parent; // Parent process
sysproc.c/kernel
uint64
sys_trace(void)
{
int trace_code;
argint(0, &trace_code);
struct proc *p = myproc();
p->trace_code = trace_code;
return 0;
}
proc.c/kernel
int
fork(void)
{
int i, pid;
struct proc *np;
struct proc *p = myproc();
// Allocate process.
if((np = allocproc()) == 0){
return -1;
}
// Copy user memory from parent to child.
if(uvmcopy(p->pagetable, np->pagetable, p->sz) < 0){
freeproc(np);
release(&np->lock);
return -1;
}
np->sz = p->sz;
// copy saved user registers.
*(np->trapframe) = *(p->trapframe);
// ======add code begin========
// copy trace code
np->trace_code = p->trace_code;
//===========end===============
// Cause fork to return 0 in the child.
np->trapframe->a0 = 0;
......
syscall.c/kernel
#include "syscall.h"
#include "defs.h"
// ======add code begin========
const char* syscall_names[] = {"fork","exit","wait","pipe","read","kill","exec","fstat","chdir","dup","getpid","sbrk","sleep","uptime","open","write","mknod","unlink","link","mkdir","close","trace"
};
//===========end===============
....
extern uint64 sys_link(void);
extern uint64 sys_mkdir(void);
extern uint64 sys_close(void);
// ======add code begin========
extern uint64 sys_trace(void);
//===========end===============
....
[SYS_link] sys_link,
[SYS_mkdir] sys_mkdir,
[SYS_close] sys_close,
// ======add code begin========
[SYS_trace] sys_trace,
//===========end===============
};
void
syscall(void)
{
int num;
struct proc *p = myproc();
num = p->trapframe->a7;
if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
// Use num to lookup the system call function for num, call it,
// and store its return value in p->trapframe->a0
p->trapframe->a0 = syscalls[num]();
// ======add code begin========
if((p->trace_code & (1 << num)) != 0){
printf("%d: syscall %s -> %d\n",
p->pid, syscall_names[num-1], p->trapframe->a0);
}
//===========end===============
} else {
printf("%d %s: unknown sys call %d\n",
p->pid, p->name, num);
p->trapframe->a0 = -1;
}
}
Sysinfo (moderate)
重要
在这个任务中,你将添加一个系统调用 sysinfo
,用于收集有关运行系统的信息。该系统调用接受一个参数:指向 struct sysinfo
的指针(参见 kernel/sysinfo.h
)。内核应填充该结构的字段:freemem
字段应设置为可用内存的字节数,nproc
字段应设置为状态不是 UNUSED
的进程数。我们提供了一个测试程序 sysinfotest
;如果你通过了这个任务,它将打印 "sysinfotest: OK"。
一些提示:
- 将
$U/_sysinfotest
添加到Makefile
中的UPROGS
。 - 运行
make qemu
;user/sysinfotest.c
将无法编译。添加系统调用sysinfo
,遵循与上一个任务相同的步骤。要在user/user.h
中声明sysinfo()
的原型,你需要预先声明struct sysinfo
的存在:
struct sysinfo;
int sysinfo(struct sysinfo *);
一旦你修复了编译问题,运行 `sysinfotest`;它将失败,因为你还没有在内核中实现系统调用。
sysinfo
需要将struct sysinfo
复制回用户空间;参见sys_fstat()
(kernel/sysfile.c
)和filestat()
(kernel/file.c
)以了解如何使用copyout()
完成此操作的示例。- 要收集可用内存量,请在
kernel/kalloc.c
中添加一个函数。 - 要收集进程数,请在
kernel/proc.c
中添加一个函数。
实验代码
usys.pl/user
......
entry("uptime");
entry("trace");
// ======add code begin========
entry("sysinfo");
//===========end===============
user.n/user
.....
char* sbrk(int);
int sleep(int);
int uptime(void);
int trace(int);
// ======add code begin========
int sysinfo(struct sysinfo *);
//===========end===============
.....
syscall.h/kernel
....
#define SYS_close 21
#define SYS_trace 22
// ======add code begin========
#define SYS_sysinfo 23
//===========end===============
defs.h/kernel
......
// kalloc.c
void* kalloc(void);
void kfree(void *);
void kinit(void);
// ======add code begin========
int countMem(void);
//===========end===============
......
int either_copyout(int user_dst, uint64 dst, void *src, uint64 len);
int either_copyin(void *dst, int user_src, uint64 src, uint64 len);
void procdump(void);
// ======add code begin========
int countProcess(void);
//===========end===============
......
syscall.c/kernel
......
extern uint64 sys_close(void);
extern uint64 sys_trace(void);
// ======add code begin========
extern uint64 sys_sysinfo(void);
//===========end===============
......
[SYS_close] sys_close,
[SYS_trace] sys_trace,
// ======add code begin========
[SYS_sysinfo] sys_sysinfo,
//===========end===============
};
......
kalloc.c/kernel
int countMem(void){
struct run *r;
int size = 0;
acquire(&kmem.lock);
r = kmem.freelist;
while (r)
{
size += PGSIZE;
r = r->next;
}
release(&kmem.lock);
return size;
}
proc.c/kernel
int countProcess(void){
int count = 0;
struct proc *p;
for(p = proc; p < &proc[NPROC]; p++){
if(p->state != UNUSED)
count++;
}
return count;
}
sysproc.c/kernel
......
#include "proc.h"
#include "sysinfo.h"
......
uint64
sys_sysinfo(void){
struct proc *p = myproc();
struct sysinfo si;
uint64 info;
argaddr(0,&info);
si.freemem = countMem();
si.nproc = countProcess();
if(copyout(p->pagetable, info, (char *)&si, sizeof(si)) < 0)
return -1;
return 0;
}