MIT 6.s081 实验解析——labs2

系列文章目录

MIT 6.s081 实验解析——labs1
MIT 6.s081 实验解析——labs2


文章目录

  • 系列文章目录
  • 测试判断流程
    • System call tracing
    • sysinfo![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/ab9ca34f1fc64b6aa1df74613dc1a397.png)


测试判断流程

  1. 完成代码后将.c文件放入user文件夹中
  2. 在makefile文件的UPROGS处添加要测试的文件,如要添加的是sleep.c,则写为_sleep。
    在这里插入图片描述
  3. 重新编译xv6
make qemu
  1. 退出qemu,在文件夹下输入
./grade-lab-util <文件名>

//以sleep为例
./grade-lab-util sleep

System call tracing

MIT 6.s081 实验解析——labs2_第1张图片

trace 32 grep hello README

以上述指令来说,这个实验想要实现的效果是,跟踪grep hello README过程中所有的系统调用,其中32为掩码,是要跟踪的系统调用种类,将32变为2进制,根据下图,在要跟踪的系统调用位置置1。MIT 6.s081 实验解析——labs2_第2张图片
所以先更改kernel/proc.h的进程结构体,新增掩码:

    struct proc {
      ...
      int mask;
    };

为了实现进程间传递参数,需添加对mask的拷贝,在kernel/proc.c的fork定义中:

    int
    fork(void)
    {
      ...
      // Cause fork to return 0 in the child.
      np->trapframe->a0 = 0;
      np->mask = p->mask;
      ...
    }

接下来就是要去完成系统调用 trace 的函数定义,在该系统调用会接收用户态传递的参数,并将其赋值给mask,我们将定义写在kernel/sysproc.c中:

    uint64
    sys_trace(void){
      int n;
      argint(0,&n);//接收参数
      myproc()->mask = n;//赋给mask
      return 0;
    }

为了在syscall中能调用sys_trace,我们需要将其函数入口地址存入数组static uint64 (*syscalls[])(void)中,在其末尾加入[SYS_trace] sys_trace,即可。
最后我们要在syscall调用完系统调用后,通过本进程的mask来确认这个系统调用是不是被追踪的,若是则输出相关信息,为了方便信息输出,我们为其定义一个字符串数组,修改的文件为kernel/syscall.c:

    char *str[]={
    [SYS_fork]    "syscall fork",
    [SYS_exit]    "syscall exit",
    [SYS_wait]    "syscall wait",
    [SYS_pipe]    "syscall pipe",
    [SYS_read]    "syscall read",
    [SYS_kill]    "syscall kill",
    [SYS_exec]    "syscall exec",
    [SYS_fstat]   "syscall fstat",
    [SYS_chdir]   "syscall chdir",
    [SYS_dup]     "syscall dup",
    [SYS_getpid]  "syscall getpid",
    [SYS_sbrk]    "syscall sbrk",
    [SYS_sleep]   "syscall sleep",
    [SYS_uptime]  "syscall uptime",
    [SYS_open]    "syscall open",
    [SYS_write]   "syscall write",
    [SYS_mknod]   "syscall mknod",
    [SYS_unlink]  "syscall unlink",
    [SYS_link]    "syscall link",
    [SYS_mkdir]   "syscall mkdir",
    [SYS_close]   "syscall close",
    [SYS_trace]   "syscall trace",
    };
    void
    syscall(void)
    {
      int num;
      struct proc *p = myproc();

      num = p->trapframe->a7; //系统调用号
      if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
        // Use num to lookup the system call function for num, call it,
        // and store its return value in p->trapframe->a0
        p->trapframe->a0 = syscalls[num](); //系统调用的返回值
        if((p->mask >> num) & 1) //若该系统调用被跟踪
        	printf("%d: %s -> %d\n",p->pid,str[num],p->trapframe->a0);//输出信息
      } else {
        printf("%d %s: unknown sys call %d\n",
                p->pid, p->name, num);
        p->trapframe->a0 = -1;
      }
    }

描述一下整个系统调用的流程:核心点就在于可以通过usys.pl文件里对系统调用的定义,使得可以在用户空间调用系统调用。
MIT 6.s081 实验解析——labs2_第3张图片
在用户空间的trace.c定义了trace调用,然后通过ECALL指令触发向内核态的切换,将对应的系统调用号和参数存入寄存器,切换至内核态之后由syscall函数对调用进行响应,然后调用对应的系统调用。处理完成之后将结果返还给用户空间,再切换回用户态,完成一次系统调用。

sysinfoMIT 6.s081 实验解析——labs2_第4张图片

整体流程和trace差不多,获取非unused的进程数,核心就是遍历进程结构体数组proc,并判断其元素的state。

    uint64 get_used_proc(){
      struct proc *p;
      uint64 n = 0;
      for(p = proc; p < &proc[NPROC]; p++) {
        if(p->state != UNUSED) 
          n++;
      }
      return n;
    }

获取空闲内存,通过查看文件kernel/kalloc.c可知每个物理内存页的单位是PGSIZE=4096字节, 以一个单链表的形式管理空闲内存页,我们只需遍历该单链表获取空闲页数,每页计一个PGSIZE即可。

    uint64 get_free_memory(){
      uint64 n=0;
      struct run* r = kmem.freelist;
      while(r){
        r=r->next;
        n += PGSIZE;
      }
      return n;
    }

将这些信息填入sysinfo结构体,然后返还给用户空间。

    uint64 sys_sysinfo(){
      uint64 st;
      argaddr(0, &st);//获取从用户空间传入的指针。
      struct sysinfo p;//将信息存在结构体中
      p.nproc = get_used_proc();
      p.freemem = get_free_memory();
      if(copyout(myproc()->pagetable, st, (char *)&p, sizeof(p)) < 0)//拷贝回用户空间
        return -1;
      return 0;
    }

你可能感兴趣的:(c,mit,6.s081,操作系统,xv6)