先切到syscall分支:
$ git fetch
$ git checkout syscall
$ make clean
原文要求
几点Hints:
Add $U/_trace to UPROGS in Makefile
Run make qemu and you will see that the compiler cannot compile user/trace.c, because the user-space stubs for the system call don't exist yet: add a prototype for the system call to user/user.h, a stub to user/usys.pl, and a syscall number to kernel/syscall.h. The Makefile invokes the perl script user/usys.pl, which produces user/usys.S, the actual system call stubs, which use the RISC-V ecall instruction to transition to the kernel. Once you fix the compilation issues, run trace 32 grep hello README; it will fail because you haven't implemented the system call in the kernel yet.
Add a sys_trace() function in kernel/sysproc.c that implements the new system call by remembering its argument in a new variable in the proc structure (see kernel/proc.h). The functions to retrieve system call arguments from user space are in kernel/syscall.c, and you can see examples of their use in kernel/sysproc.c.
Modify fork() (see kernel/proc.c) to copy the trace mask from the parent to the child process.
Modify the syscall() function in kernel/syscall.c to print the trace output. You will need to add an array of syscall names to index into.
If a test case passes when you run it inside qemu directly but you get a timeout when running the tests using make grade, try testing your implementation on Athena. Some of tests in this lab can be a bit too computationally intensive for your local machine (especially if you use WSL).
逐一理解,首先Makefile文件就不多说了
$U/_trace\
第二个hint需要我们在user/user.h
和user/usys.pl
和kernel/syscall.h
添加相应的内容,可以根据上文来推测。
in user/user.h
// system calls
int fork(void);
......
......
int uptime(void);
// add a prototype for the system call
int trace(int);
in user/usys.pl
......
entry("uptime");
# a stub
entry("trace");
in kernel/syscall.h
// System call numbers
#define SYS_fork 1
......
......
#define SYS_trace 22
完成上述操作后,运行trace 32 grep hello README
仍然无法成功,因为在内核中的系统调用还没有实现。
第三个hint很重要,
Add a sys_trace() function in kernel/sysproc.c that implements the new system call by remembering its argument in a new variable in the proc structure (see kernel/proc.h).
解释:
在kernel/sysproc.c
中添加一个sys_trace()
函数,这个函数来实现这个新的系统调用。而这个新的系统调用,是通过在proc结构体中的新变量中来记住它的参数。
也就是说,要做两件事,一个是在kernel/sysproc.c
中添加一个sys_trace()
函数;一个是要在proc结构体中添加新的变量。
接下来看第三个hint的第二句话,
The functions to retrieve system call arguments from user space are in kernel/syscall.c, and you can see examples of their use in kernel/sysproc.c.
在kernel/syscall.c
中有很多 从用户空间 检索/获取(retrieve)系统调用参数 的函数,具体例子可以参考kernel/sysproc.c
通过阅读kernel/syscall.c
可以发现有很多函数
int fetchaddr(uint64 addr,uint64 *ip);
int fetchstr(uint64 addr,char* buf,int max);
static uint64 argraw(int n);
void argint(int n,int* ip);
void argaddr(int n,uint64 *ip);
int argstr(int n,char* buf,int max);
阅读这些函数的注释,结合我们需要的命令行trace 32 grep hello README
可以看见,我们主要是要获取32
这个参数,所以我着重阅读了argint()
函数。而第三个hint第二句话也说明了在kernel/sysproc.c
中有些许使用argint()
的例子。
大概的感性认识就是,argint(int n,int* ip)
将系统调用参数中第n个32bit数存储到ip中。
那么我根据提示,先在kernel/sysproc.c
中添加sys_trace()
函数:
uint64
sys_uptime(void)
{
...
}
//add a sys_trace()
uint64
sys_trace(void)
{
printf("sys_trace: test");//目前还不知道填什么
}
其实这里可以试着在xv6系统中运行一下trace 32 grep hello README
可以发现这一行起作用了。
跟着提示,再去kernel/proc.h
中阅读一下proc结构体。到这里就有些许眉头了,在proc结构体中可以发现一个进程的一些属性,比如pid,该进程的父进程等等。这个Lab让我们实现trace
,那么需要的就是trace mask
了,用于确定具体哪一个系统调用需要追踪。比如trace 32 grep hello README
中,32
就是1 << SYS_read
也就是1左移5位,表示追踪read
系统调用。
那么我们在proc结构体中添加trace_mask
struct proc {
struct spinlock lock;
......
int pid;
struct proc *parent;
......
//trace mask
int trace_mask;
};
接着到kernel/syscall.c
中先做些简单工作。
// Prototypes for the functions that handle system calls.
extern unit64 sys_fork(void);
......
extern uint64 sys_trace(void);
......
static uint64 (*syscalls[])(void) = {
[SYS_fork] sys_fork,
......
[SYS_trace] sys_trace,
};
提示还说,可以从kernel/sysproc.c
中阅读如何获取参数的例子。
上文我提及了argint()
,另外一个就是用myproc()
函数来获取当前进程的相关属性。
在填写sys_trace()
函数之前,我们先暂停,继续往下看hint
第四个hint照做就行,读到fork()
后有一种恍然大悟的感觉,这里就是父子进程共享内存段的代码实现。
我们把proc结构体中的新变量trace_mask
复制给子进程。
int
fork(void)
{
int i,pid;
struct proc *np;
struct proc *p = myproc();
......
// copy trace mask
np->trace_mask=p->trace_mask;
......
}
第五个hint就是真正实现trace
了,在这里先捋一下整个trace
的过程。
首先trace 32 grep hello README
中,
用户态运行trace 32
,在user/trace.c
中的main函数可以看见调用了trace()
函数。
然后就从用户态进入到内核态,执行sys_trace()
函数,这个函数通过某种方式把参数(mask)传给syscall()
函数,然后syscall()
函数输出结果,这个结果包括当前进程的pid,系统调用的名字及其返回值.
这里所说的"通过某种方式"其实已经明了,使用myproc()
对当前进程的proc结构体中的trace_mask赋值即可.
in kernel/sysproc.c
:
uint64
sys_trace(void)
{
struct proc *p = myproc();
int mask;
argint(0, &mask);
p->trace_mask=mask;
return 0;
}
in kernel/syscall.c
:
// syscall_names
char* syscall_names[] = {
"fork",
"exit",
......
"trace",
}
void
syscall(void)
{
int num;
struct proc *p = myproc();
num = p->trapframe->a7;
if(num > 0 && num < NELEM(syscalls) && syscalls[num] ) {
//这里num是系统调用的数 比如SYS_read是5,
//然后syscalls[nums]()返回的是系统调用的返回值,存到a0寄存器里面.
p->trapframe->a0 = syscalls[nums]();
// solution
if( (p->trace_mask >> num) & 1 ){
printf("%d: syscall %s -> %d\n",p->pid,syscall_names[num - 1],p->trapframe->a0);
}
}else{
......
}
}
经检验,通过测试.
原文要求:
前两点提示与trace
实验类似,在Makefile里面添加一行,在user/user.h
,user/usys.pl
,kernel/syscall.h
以及kernel/syscall.c
中添加类似的代码即可。
注意在user/user.h
中要添加一个
struct sysinfo;
int sysinfo(struct sysinfo*);
到这里编译没有问题。
查看sysinfo
结构体内部,有两个东西。一个是freemem
一个是nproc
(其状态不能是UNUSED)
接下来第三个提示就比较重要。
目的是把结构体sysinfo从内核空间复制到用户空间。而需要我们借鉴sys_fstat()
和filestat()
两个函数的实例,来学会使用copyout()
copyout()
个人理解:
int copyout(pagetable_t pagetable, uint64 dstva, char* src, uint64 len);
按照注释也能感性认识,从src中取len字节数据传给dstva(destination vitural address)的用户空间。
那么结合sys_fstat()
和filestat()
,在kernel/sysproc.c
把函数写完。
uint64
sys_sysinfo(void)
{
struct sysinfo si;// sysinfo
uint64 addr; // user pointer to struct sysinfo
struct proc* p = myproc();
argaddr(0, &addr);// only one argument: struct sysinfo* ,so we use argaddr()
// waiting for implement
si.freemem = -1;
si.nproc = -1;
// copy from kernel to user space;
if(copyout(p->pagetable, addr, (char*)&si, sizeof(si)) < 0){
return -1;
}
return 0;
}
第四第五个提示也很明确了,前往kernel/kalloc.c
和kernel/proc.c
添加两个函数,获取sysinfo结构体中的两个变量:freemem
和nproc
即可。
in kernel/kalloc.c
:
这个文件中kmem
是全局的结构体变量,内部有个锁lock,以及一个链表freelist;我们数完freelist之后,数量乘以每页的大小(4096)即可。(注意看kalloc()函数,分配以PGSIZE为单位)
uint64
collect_freemem(void)
{
struct run *r;
uint64 cnt = 0;// number of freemem
acquire(&kmem.lock);
r = kmem.freelist;
while(r){
r = r->next;
cnt++;
}
release(&kmem.lock);
return PGSIZE * cnt;// bytes per page times cnt;
}
in kernel/proc.c
:
数进程数很简单,注意是‘非UNUSED’进程。阅读这个文件可以知道有个进程池类似的概念(变量proc
数组)就从这里数。
uint64
collect_nproc(void)
{
struct proc* p;
uint64 cnt = 0;
for(p = proc; p < &proc[NPROC]; p++){
if(p->state != UNUSED){
cnt++;
}
}
return cnt;
}
把这俩实现的函数在kernel/sysproc.c
中声明,然后在sys_sysinfo()
中调用即可。
uint64 collect_freemem(void);
uint64 collect_nproc(void);
......
uint64
sys_sysinfo(void)
{
struct sysinfo si;// sysinfo
uint64 addr; // user pointer to struct sysinfo
struct proc* p = myproc();
argaddr(0, &addr);// only one argument: struct sysinfo* ,so we use argaddr()
// implement
si.freemem = collect_freemem();
si.nproc = collect_nproc();
// copy from kernel to user space;
if(copyout(p->pagetable, addr, (char*)&si, sizeof(si)) < 0){
return -1;
}
return 0;
}
运行sysinfotest
这个实验就此结束了。