lab2: syscall

trace

本实验主要是实现一个追踪系统调用的函数,那么首先根据提示定义trace系统调用,并修复编译错误。

首先看一下user/trace.c的内容,主要的代码如下

  1. if (trace(atoi(argv[1])) < 0) {
  2. fprintf(2, "%s: trace failed\n", argv[0]);
  3. exit(1);
  4. }
  5. for(i = 2; i < argc && i < MAXARG; i++){
  6. nargv[i-2] = argv[i];
  7. }
  8. exec(nargv[0], nargv);

它首先调用trace(int),然后将命令行中的参数argv复制到nargv中,同时删去前两个参数,例如

  1. argv = trace 32 grep hello README
  2. nargv = grep hello README

那么,根据提示,我们首先再proc结构体中添加一个数据字段,用于保存trace的参数。并在sys_trace()的实现中实现参数的保存

  1. // kernel/proc.h
  2. struct proc {
  3. // ...
  4. int trace_mask; // trace系统调用参数
  5. };
  6. // kernel/sysproc.c
  7. uint64
  8. sys_trace(void)
  9. {
  10. // 获取系统调用的参数
  11. argint(0, &(myproc()->trace_mask));
  12. return 0;
  13. }

接下来应当考虑如何进行系统调用追踪了,根据提示,这将在syscall()函数中实现。下面是实现代码,需要注意的是条件判断中使用了&而不是==,这是因为在实验说明书的例子中,trace 2147483647 grep hello README将所有31个低位置为1,使得其可以追踪所有的系统调用。

  1. void
  2. syscall(void)
  3. {
  4. int num;
  5. struct proc *p = myproc();
  6. num = p->trapframe->a7; // 系统调用编号,参见书中4.3节
  7. if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
  8. p->trapframe->a0 = syscalls[num](); // 执行系统调用,然后将返回值存入a0
  9. // 系统调用是否匹配
  10. if ((1 << num) & p->trace_mask)
  11. printf("%d: syscall %s -> %d\n", p->pid, syscalls_name[num], p->trapframe->a0);
  12. } else {
  13. printf("%d %s: unknown sys call %d\n",
  14. p->pid, p->name, num);
  15. p->trapframe->a0 = -1;
  16. }
  17. }

在上面的代码中,我们还有一些引用的变量尚未定义,在syscall.c中定义他们

  1. // ...
  2. extern uint64 sys_trace(void);
  3. static uint64 (*syscalls[])(void) = {
  4. // ...
  5. [SYS_trace] sys_trace,
  6. };
  7. static char *syscalls_name[] = {
  8. [SYS_fork] "fork",
  9. [SYS_exit] "exit",
  10. [SYS_wait] "wait",
  11. [SYS_pipe] "pipe",
  12. [SYS_read] "read",
  13. [SYS_kill] "kill",
  14. [SYS_exec] "exec",
  15. [SYS_fstat] "fstat",
  16. [SYS_chdir] "chdir",
  17. [SYS_dup] "dup",
  18. [SYS_getpid] "getpid",
  19. [SYS_sbrk] "sbrk",
  20. [SYS_sleep] "sleep",
  21. [SYS_uptime] "uptime",
  22. [SYS_open] "open",
  23. [SYS_write] "write",
  24. [SYS_mknod] "mknod",
  25. [SYS_unlink] "unlink",
  26. [SYS_link] "link",
  27. [SYS_mkdir] "mkdir",
  28. [SYS_close] "close",
  29. [SYS_trace] "trace",
  30. };

sysinfo

  • kernel/kalloc.c中添加一个函数用于获取空闲内存量
  1. struct run {
  2. struct run *next;
  3. };
  4. struct {
  5. struct spinlock lock;
  6. struct run *freelist;
  7. } kmem;

内存是使用链表进行管理的,因此遍历kmem中的空闲链表就能够获取所有的空闲内存,如下

  1. void
  2. freebytes(uint64 *dst)
  3. {
  4. *dst = 0;
  5. struct run *p = kmem.freelist; // 用于遍历
  6. acquire(&kmem.lock);
  7. while (p) {
  8. *dst += PGSIZE;
  9. p = p->next;
  10. }
  11. release(&kmem.lock);
  12. }
  • kernel/proc.c中添加一个函数获取进程数

遍历proc数组,统计处于活动状态的进程即可,循环的写法参考scheduler函数

  1. void
  2. procnum(uint64 *dst)
  3. {
  4. *dst = 0;
  5. struct proc *p;
  6. for (p = proc; p < &proc[NPROC]; p++) {
  7. if (p->state != UNUSED)
  8. (*dst)++;
  9. }
  10. }
  • 实现sys_sysinfo,将数据写入结构体并传递到用户空间
  1. uint64
  2. sys_sysinfo(void)
  3. {
  4. struct sysinfo info;
  5. freebytes(&info.freemem);
  6. procnum(&info.nproc);
  7. // 获取虚拟地址
  8. uint64 dstaddr;
  9. argaddr(0, &dstaddr);
  10. // 从内核空间拷贝数据到用户空间
  11. if (copyout(myproc()->pagetable, dstaddr, (char *)&info, sizeof info) < 0)
  12. return -1;
  13. return 0;
  14. }