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; }