diff --git a/SUMMARY.md b/SUMMARY.md index 5cc7681..50d179b 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -106,6 +106,7 @@ * [Lab11: Network](labs/requirements/lab11.md) * [实验解析](labs/answers/summary.md) * [Lab1: Util](labs/answers/lab1.md) + * [Lab2: Syscall](labs/answers/lab2.md) * [Lab4: Traps](labs/answers/lab4.md) * [Lab5: Xv6 lazy page allocation](labs/answers/lab5.md) * [Lab6: Copy-on-Write Fork for xv6](labs/answers/lab6.md) diff --git a/book.pdf b/book.pdf new file mode 100644 index 0000000..929173f Binary files /dev/null and b/book.pdf differ diff --git a/labs/answers/lab2.md b/labs/answers/lab2.md new file mode 100644 index 0000000..5536e97 --- /dev/null +++ b/labs/answers/lab2.md @@ -0,0 +1,177 @@ +# lab2: syscall + +## trace + +本实验主要是实现一个追踪系统调用的函数,那么首先根据提示定义`trace`系统调用,并修复编译错误。 + +首先看一下*user/trace.c*的内容,主要的代码如下 + +```c +if (trace(atoi(argv[1])) < 0) { + fprintf(2, "%s: trace failed\n", argv[0]); + exit(1); +} +for(i = 2; i < argc && i < MAXARG; i++){ + nargv[i-2] = argv[i]; +} +exec(nargv[0], nargv); +``` + +它首先调用`trace(int)`,然后将命令行中的参数`argv`复制到`nargv`中,同时删去前两个参数,例如 + +```text +argv = trace 32 grep hello README +nargv = grep hello README +``` + +那么,根据提示,我们首先再`proc`结构体中添加一个数据字段,用于保存`trace`的参数。并在`sys_trace()`的实现中实现参数的保存 + +```c +// kernel/proc.h +struct proc { + // ... + int trace_mask; // trace系统调用参数 +}; + +// kernel/sysproc.c +uint64 +sys_trace(void) +{ + // 获取系统调用的参数 + argint(0, &(myproc()->trace_mask)); + return 0; +} +``` + +接下来应当考虑如何进行系统调用追踪了,根据提示,这将在`syscall()`函数中实现。下面是实现代码,需要注意的是条件判断中使用了`&`而不是`==`,这是因为在实验说明书的例子中,`trace 2147483647 grep hello README`将所有31个低位置为1,使得其可以追踪所有的系统调用。 + +```c +void +syscall(void) +{ + int num; + struct proc *p = myproc(); + + num = p->trapframe->a7; // 系统调用编号,参见书中4.3节 + if(num > 0 && num < NELEM(syscalls) && syscalls[num]) { + p->trapframe->a0 = syscalls[num](); // 执行系统调用,然后将返回值存入a0 + + // 系统调用是否匹配 + if ((1 << num) & p->trace_mask) + printf("%d: syscall %s -> %d\n", p->pid, syscalls_name[num], p->trapframe->a0); + } else { + printf("%d %s: unknown sys call %d\n", + p->pid, p->name, num); + p->trapframe->a0 = -1; + } +} +``` + +在上面的代码中,我们还有一些引用的变量尚未定义,在*syscall.c*中定义他们 + +```c +// ... +extern uint64 sys_trace(void); + +static uint64 (*syscalls[])(void) = { +// ... +[SYS_trace] sys_trace, +}; + +static char *syscalls_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", +}; +``` + +## sysinfo + +* 在*kernel/kalloc.c*中添加一个函数用于获取空闲内存量 + +```c +struct run { + struct run *next; +}; + +struct { + struct spinlock lock; + struct run *freelist; +} kmem; +``` + +内存是使用链表进行管理的,因此遍历`kmem`中的空闲链表就能够获取所有的空闲内存,如下 + +```c +void +freebytes(uint64 *dst) +{ + *dst = 0; + struct run *p = kmem.freelist; // 用于遍历 + + acquire(&kmem.lock); + while (p) { + *dst += PGSIZE; + p = p->next; + } + release(&kmem.lock); +} +``` + +* 在*kernel/proc.c*中添加一个函数获取进程数 + +遍历`proc`数组,统计处于活动状态的进程即可,循环的写法参考`scheduler`函数 + +```c +void +procnum(uint64 *dst) +{ + *dst = 0; + struct proc *p; + for (p = proc; p < &proc[NPROC]; p++) { + if (p->state != UNUSED) + (*dst)++; + } +} +``` + +* 实现`sys_sysinfo`,将数据写入结构体并传递到用户空间 + +```c +uint64 +sys_sysinfo(void) +{ + struct sysinfo info; + freebytes(&info.freemem); + procnum(&info.nproc); + + // 获取虚拟地址 + uint64 dstaddr; + argaddr(0, &dstaddr); + + // 从内核空间拷贝数据到用户空间 + if (copyout(myproc()->pagetable, dstaddr, (char *)&info, sizeof info) < 0) + return -1; + + return 0; +} +``` diff --git a/labs/requirements/lab2.md b/labs/requirements/lab2.md index f447d6d..2298492 100644 --- a/labs/requirements/lab2.md +++ b/labs/requirements/lab2.md @@ -64,7 +64,7 @@ $ **提示:** - 在***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`;但由于您还没有在内核中实现系统调用,执行将失败。 +- 运行`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()`函数以打印跟踪输出。您将需要添加一个系统调用名称数组以建立索引。