Lab2

mit 6.s081 lab2

Lab2怎么说呢,总觉得挺奇怪的,第一次接触系统调用代码,有点小懵,一个trace就做了我1个多小时。

lab1的时候只添加函数还是挺友好的,就跟平常写代码一样,这个系统调用就绕来绕去的,贼麻烦,也算体验到工程代码的<<乐趣>>?

不过还是得称赞一下lab2得Hints,没有它感觉根本做不下去:::

System call tracing(moderate)

首先看一下Hints,先pass掉那些test用例,后面再看:

lab2提示and我的提示:

  • MakefileUPROGS中添加$U/_trace

    • 这一步比较简单,就像lab1那样在makefile里面添加即可

  • 运行make qemu,您将看到编译器无法编译user/trace.c,因为系统调用的用户空间存根还不存在:将系统调用的原型添加到user/user.h,存根添加到user/usys.pl,以及将系统调用编号添加到kernel/syscall.hMakefile调用perl脚本user/usys.pl,它生成实际的系统调用存根user/usys.S,这个文件中的汇编代码使用RISC-V的ecall指令转换到内核。一旦修复了编译问题(注:如果编译还未通过,尝试先*make clean*,再执行*make qemu*),就运行trace 32 grep hello README;但由于您还没有在内核中实现系统调用,执行将失败。

    • 这里就纯粹按照提示去干

    • 首先到user/user.h添加一个 int trace(int); (仿照上面那些,看着就知道要干嘛了

    • 然后到user/usys.pl添加存根 entry("trace");

    • 最后到kernel/syscall.h添加 #define SYS_trace 22

    • 编译完可以看到user/usys.S里面新增了

      trace: 
      li a7, SYS_trace 
      ecall  
      ret
  • kernel/sysproc.c中添加一个sys_trace()函数,它通过将参数保存到proc结构体(请参见kernel/proc.h)里的一个新变量中来实现新的系统调用。从用户空间检索系统调用参数的函数在kernel/syscall.c中,您可以在kernel/sysproc.c中看到它们的使用示例。

  • 修改fork()(请参阅kernel/proc.c)将跟踪掩码从父进程复制到子进程。

  • 修改kernel/syscall.c中的syscall()函数以打印跟踪输出。您将需要添加一个系统调用名称数组以建立索引。

跟着上面到第二步就可以 make qemu 进去看看了,然后输一下测试用例试试,就会出现 trace fail

接下来就是 三四五 这三步来真正写代码

第三步主要是实现sys_trace 函数:

首先需要按照提示的:它通过将参数保存到proc结构体(请参见kernel/proc.h)里的一个新变量中来实现新的系统调用。可以知道,需要在 proc.h里面的结构体添加个新变量:

// Per-process state
struct proc {
  struct spinlock lock;

  // p->lock must be held when using these:
  enum procstate state;        // Process state
  struct proc *parent;         // Parent process
  void *chan;                  // If non-zero, sleeping on chan
  int killed;                  // If non-zero, have been killed
  int xstate;                  // Exit status to be returned to parent's wait
  int pid;                     // Process ID

  // 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)
  pagetable_t pagetable;       // User page table
  struct trapframe *trapframe; // data page for trampoline.S
  struct context context;      // swtch() here to run process
  struct file *ofile[NOFILE];  // Open files
  struct inode *cwd;           // Current directory
  char name[16];               // Process name (debugging)
  // 新增变量
  int traceMask;                   // Parameters needed for trace
};

然后按照提示就可以继续在 kernel/sysproc.c 里面添加一个 sys_trace 函数:

uint64
sys_trace(void) 
{
  int trace;
  // 获取整数参数
  // argint:Fetch the nth 32-bit system call argument.
  if (argint(0, &trace) < 0) 
    return -1;
  // printf("%d\n",trace); // debug
  myproc()->traceMask = trace;
  return 0;
}

这个函数的实现就是说如果系统调用 sys_trace called,就将 traceMask 赋值为 命令行带进来的参数,如:

trace 32 grep hello README // 这个就将 traceMask 赋值为32
trace 2147483647 grep hello README // // 这个就将 traceMask 赋值为 2147483647 

然后到第四步,在kernel/proc.c的 fork() 加一行语句将 跟踪掩码从父进程复制到子进程:

int
fork(void) 
{
...
  // trace to child
  np->traceMask = p->traceMask;
...
}

第五步:修改kernel/syscall.c中的syscall()函数以打印跟踪输出,按照提示需要先建立一个名称索引。(wc,第一次见到这种写法,属实牛皮

// hints:修改kernel/syscall.c中的syscall()函数以打印跟踪输出。您将需要添加一个系统调用名称数组以建立索引。
static char* syscallsName[] = {
[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",
};

最后就是要修改 syscall()函数来输出了

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]();
  } else {
    printf("%d %s: unknown sys call %d\n",
            p->pid, p->name, num);
    p->trapframe->a0 = -1;
  }

  // if(num == SYS_trace) {
  //   printf("trace\n");
  // }
  // 按题目给的三个案例可以推断出这里是要按位与的,像第一个给32,
  // 只输出traceMask==32的。
  // 第二个说是:2147483647将所有31个低位置为1。然后所有都输出了,因此需要用 & 
  // 先用 1<<num 转一下,因为题目就这样要求
  // xv6 4.3里面说 p->trapframe->a0 存储的是 syscall的返回值,上面那个if也可以看出来
  if (p->traceMask & (1 << num)) {
    printf("%d: syscall %s -> %d\n",p->pid,syscallsName[num],p->trapframe->a0);
  }
}

finally : 有个通过的小细节啊,上面的printf中间全要用空格,我刚开始用 tab,test还给我报了三个错....

== Test trace 32 grep == trace 32 grep: OK (1.3s) 
    (Old xv6.out.trace_32_grep failure log removed)
== Test trace all grep == trace all grep: OK (1.0s) 
    (Old xv6.out.trace_all_grep failure log removed)
== Test trace nothing == trace nothing: OK (0.9s) 
== Test trace children == trace children: OK (11.8s) 
    (Old xv6.out.trace_children failure log removed)

最后我自己总结一下调用的,因为写完这文章再回顾还是得继续看 Hints:

Makefile调用perl脚本user/usys.pl,它生成实际的系统调用存根user/usys.S ->

sh 调用 trace 指令 ->

trace.c 将 argv[1] 传入 user/user.h 的 int trace(int) ->

user/user.h的存根在user/usys.pl 的entry("trace"); 进入系统调用 ->

syscall()在if语句里面进入kernel/sysproc.c中,执行 sys_trace(),将argv[1]添加到myproc()->traceMask中 ->

进程状态就被trace标记上了,然后转回trace.c,执行exec ->

在exec源码中这个struct proc *p = myproc();是带有traceMask的 ->

然后一执行syscall就会被判断到,然后输出我们想要跟踪的状态,如果是fork了的话,也会通过我们在fork里面给子进程添加的traceMask进而跟踪子进程

可能总结的不对,之后学明白了再回来看看需不需要改

Sysinfo(moderate)

在Makefile add $U/_sysinfotest\

在user.h add

struct sysinfo;
int sysinfo(struct sysinfo *);

在usys.pl add entry("sysinfo");

在kernel/syscall.h添加 #define SYS_sysinfo 23

在sysproc.c添加:

// 写法参照 sys_fstat()(kernel/sysfile.c)和filestat()(kernel/file.c)
uint64
sys_sysinfo(void)
{
  struct sysinfo s;
  // 因为sysinfotest里面传过来的是  struct sysinfo *;所以拿地址就行
  uint64 addr;
  if (argaddr(0,&addr) < 0) {
    return -1;
  }
  // get free memory size
  s.freemem = getfreem();
  // get process numbers: state != unused
  s.nproc = getprocinfo();
  // 使用copyout将数据传回给用户
  struct proc *p = myproc();
  if (copyout(p->pagetable,addr,(char *)&s,sizeof(s))<0) {
    return -1;
  }
  return 0;
}

在defs.h的不同位置添加上面两个函数,根据文件的注释就行

在kalloc.c添加:

我最开始是直接count++,发现test的时候错了,报的那个正确数是我得到的 4096倍,因此发现了PGSIZE的含义: #define PGSIZE 4096 // bytes per page

// 获取空闲内存量
int getfreem() {
  struct run *r;
  // 我猜kmem存放的应该就是页的集合
  // freelist 就是空闲列表,存放空闲内存地址,在ostep 15章有介绍
  r = kmem.freelist;
  int count = 0;
  while (r)
  {
    count+=PGSIZE;
    r = r->next;
  }
  return count;
}

在proc.c添加:

int 
getprocinfo() 
{
  struct proc *p;
  int count = 0;
  // NPROC 是最大进程数
  for(p = proc;p < &proc[NPROC];p++) {
    if(p->state != UNUSED) count++;
  }
  return count;
}

其他一些需要的添加跟上面一样,如果要使用trace debug的话参照我上面的添加就行,感觉trace还是写的很鸡肋,在我debug的过程中没有产生任何作用,还得是老师的测试脚本

Last updated