系统调用
Lab1 主要是基于提供的系统调用接口来编写一些小工具程序,而 Lab2 则是要我们自己实现系统调用,并提供系统调用的接口。
以本次 Lab 要我们实现的 trace
调用为例,说明一下系统调用的流程:
在 user/trace.c
的第 $15$ 行,调用了属于 system call 的 trace
函数,当前执行 make qemu
是无法成功的,因为我们还没有给用户提供接口。因此,我们需要在 user/user.h
里面添加系统调用 trace
的函数声明(prototype)。
我们需要在 user/usys.pl
中追加 entry("trace");
这一行,从而添加一个 trap entry,从而实现调用 trace
时会发生 trap,从而会执行 ECALL
指令,并且会将系统调用的接口的参数(这里就是 trace
的参数)的存入寄存器 a0
、a1
等(从左往右第一个参数的地址存入 a0
,依次类推),此外,还会将 trace
对应的系统调用号存入寄存器 a7
。
之后控制权来到 kernel 中的 syscall
函数,它从 a7
中取出系统调用号,并执行系统调用号对应的 sys_func
。
这里的系统调用号可以理解为数组索引,在本次 Lab 中需要我们修改
kernel/syscall.c
和kenel/syscall.h
从而添加trace
对应的系统调用号,以及内核中trace
对应的 sys_trace 的实现)。
System call tracing
官网的提示其实是比较全面了,按提示处理,即可添加 trace
的系统调用接口:
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.
之后,则需要我们自己在 kernel/syspro.c
中实现真正执行系统调用 trace
所需要执行的操作了:
Lab 给出的提示中提到,我们需要将 trace(int mask)
的参数 mask
参数存放到 proc
结构体中的一个新成员变量中,这里我们直接用 mask
表示,于是,sys_trace
要做的其实很简单,就是取出寄存器 a0
中的地址然后解引用,得到 mask
,然后将它赋给 proc
结构体中的 mask
。
对于取出寄存器 a0
的参数,Xv6 已经给我们封装好了一个函数 argint
,参照一下 sys_exit
的实现,以及 int argint(int n, int *p)
的实现,就能知道 argint
如何使用了,第一个参数表示从哪个寄存器中取值,取出来的值放在地址 p
中。
// 修改 syscall.h,添加 #define SYS_trace 22
static uint64 (*syscalls[])(void) = {
[SYS_fork] sys_fork,
[SYS_exit] sys_exit,
[SYS_wait] sys_wait,
[SYS_pipe] sys_pipe,
[SYS_read] sys_read,
[SYS_kill] sys_kill,
[SYS_exec] sys_exec,
[SYS_fstat] sys_fstat,
[SYS_chdir] sys_chdir,
[SYS_dup] sys_dup,
[SYS_getpid] sys_getpid,
[SYS_sbrk] sys_sbrk,
[SYS_sleep] sys_sleep,
[SYS_uptime] sys_uptime,
[SYS_open] sys_open,
[SYS_write] sys_write,
[SYS_mknod] sys_mknod,
[SYS_unlink] sys_unlink,
[SYS_link] sys_link,
[SYS_mkdir] sys_mkdir,
[SYS_close] sys_close,
[SYS_trace] sys_trace,
};
// syscall for trace
uint64 sys_trace(void) {
struct proc *to_trace = myproc();
int mask;
if (argint(0, &mask) < 0) { // 取出 trace 之后的 mask 参数
return -1;
}
to_trace->mask = mask;
return 0;
}
然后需要修改 fork()
,从而使得子进程也共享同一个 mask
,这里很简单,在 fork()
的实现中添加 np->mask = p->mask;
即可。
最后还需要修改 syscall()
函数,从而当系统调用号属于 mask
使能的系统调用号时,按 Lab 要求进行 printf
。
void syscall(void) {
int num;
struct proc *p = myproc();
num = p->trapframe->a7;
if (num > 0 && num < NELEM(syscalls) && syscalls[num]) {
uint64 res = syscalls[num]();
p->trapframe->a0 = res;
int mask = p->mask;
if ((1 << num) & mask) {
printf("%d: syscall %s -> %d\n", p->pid, name[num], res);
}
} else {
printf("%d %s: unknown sys call %d\n",
p->pid, p->name, num);
p->trapframe->a0 = -1;
}
}
Sysinfo
添加接口的操作同上,我们先实现 sys_sysinfo
,由于 sysinfo
接口的参数是个指针(或者说地址),尽管该参数的地址依旧存放于 a0
寄存器中,但是我们不能再使用 argint
了,这里 Lab 给出了提示,参照 sys_fstat
的实现,我们知道了可以使用 argaddr
实现这一操作,a0
中的值被存放于 uint64 addr
中。
之后,我们需要创建一个新的 struct sysinfo
结构体变量 info
,然后将统计的 amount of free memory 和 number of processes 存放于这个结构体变量中。
之后,Lab 提示我们使用 copyout
将 info
传递到 user space,其实参照 copyout
的注释就能清楚明白 copyout
是干嘛的,怎么用了。
uint64 sys_sysinfo(void) {
uint64 addr; // user pointer to struct sysinfo
if (argaddr(0, &addr) < 0) {
return -1;
}
struct proc *p = myproc();
struct sysinfo info;
info.nproc = get_proc_num();
info.freemem = freemem_size();
if (copyout(p->pagetable, addr, (char *)&info, sizeof(info)) < 0) {
return -1;
}
return 0;
}
在 defs.h
中添加 get_proc_num
和 freemem_size
的 prototype。
分别在 kernel/kalloc.c
和 kernel/proc.c
中实现。统计 amount of free memory,从 kalloc.c
中可以发现,Xv6 是使用 external free memory list 的形式来进行 malloc
的,所以我们只需要统计 free memory list 的结点数就能知道了;而对统计 number of processes 来说,从 proc.c
的 struct proc proc[NPROC];
中可以得知,进程是以数组形式管理的,我们统计该数组中状态不为 unused
即可知 number of processes。
// kernel/kalloc.c
int freemem_size(void) {
struct run *r;
int num = 0;
for (r = kmem.freelist; r; r = r->next) {
++num;
}
return num * PGSIZE;
}
// kernel/proc
int get_proc_num(void) {
struct proc *p;
int num = 0;
for (p = proc; p < &proc[NPROC]; ++p) {
if (p->state != UNUSED) {
++num;
}
}
return num;
}
总结
这个 Lab 设计得非常精妙,做了 System call tracing 之后,对系统调用的流程就有了比较深刻的理解了,Sysinfo 则是加深理解,两个实验一起,对系统调用时,参数的传入、结果的传出,都有了大致概念。
标签:SYS,trace,mask,Xv6,sys,Lab2,num,proc From: https://www.cnblogs.com/zwyyy456/p/17551881.html