2020 mit6.s081 os Lab: xv6 traps

友情链接:全部实验哟


实验链接

https://pdos.csail.mit.edu/6.S081/2020/labs/pgtbl.html


实验

RISC-V assembly

  1. a0-a7,13保存在a2里

  2. 函数f和g直接被内联优化了,即printf的第2个参数直接是12(f(8)+1的结果).

    在这里插入图片描述

  3. 0x640

    在这里插入图片描述

  4. ra保存的是函数调用后返回的地址,由下图可以看出为0x38

    在这里插入图片描述

  5. He110 World(本人的机器是小端的,所以是这个输入,如果是大端的,输出则不一样)

    unsigned int i = 0x00646c72;
    printf("H%x Wo%s", 57616, &i);
    
  6. y的输出结果为0,保存的是a2寄存器中的值。

    printf("x=%d y=%d", 3);
    

Backtrace

该题的主要目的是打印当前进程的调用链,为实现该函数,我们首先需要了解栈的分布情况,如下图所示:

  1. stack地址从高到低进行增长;
  2. sp: 指向栈的最底端;
  3. fp: 当前栈帧的顶端;
  4. return address是地址是fp-8
  5. prev frame fp保存地址是fp-16

在这里插入图片描述

按照实验提示进行代码更改:

  1. kernel/defs.h,添加头文件。

    // printf.c
    void            printf(char*, ...);
    void            panic(char*) __attribute__((noreturn));
    void            printfinit(void);
    // begin++++
    void            backtrace(void);
    // end------
    
  2. kernel/syspro.c,在函数sys_sleep中添加调用

    uint64
    sys_sleep(void)
    {
      int n;
      uint ticks0;
       // begin+++++
       backtrace();
       // end-------
    
      if(argint(0, &n) < 0)
        return -1;
      acquire(&tickslock);
      ticks0 = ticks;
      while(ticks - ticks0 < n){
        if(myproc()->killed){
          release(&tickslock);
          return -1;
        }
        sleep(&ticks, &tickslock);
      }
      release(&tickslock);
      return 0;
    }
    
  3. kernel/riscv.h文件中,添加如下函数,该函数的主要作用是从s0寄存器读取数值。(gcc编译器将当前正在执行函数的栈帧指针保存在s0寄存器中)

    static inline uint64
    r_fp()
    {
      uint64 x;
      asm volatile("mv %0, s0" : "=r" (x) );
      return x;
    }
    
  4. kernel/printf.c文件中,添加如下函数:

    
    void
    backtrace(void)
    {
        // 获取栈帧首地址
        uint64 fpaddr = r_fp();
        // 获取当前进程在kernel中stack的最大地址
        uint64 max = PGROUNDUP(fpaddr);
        // 因为栈是从高地址至低地址增长的,所以这里用小于号判断
        while (fpaddr < max) { 
            // return address是当前fp-8
            printf("%p\n", *((uint64*)(fpaddr - 8)));
            // 调用该函数的栈帧便宜是fp - 16
            fpaddr = *((uint64*)(fpaddr - 16));
        }
    }
    
  5. kernel/printf.c文件中,在函数panic中添加对backtrace的调用:

    void
    panic(char *s)
    {
      // begin+++++  
      backtrace();
      // end+++++++  
      pr.locking = 0;
      printf("panic: ");
      printf(s);
      printf("\n");
      panicked = 1; // freeze uart output from other CPUs
      for(;;)
        ;
    }
    
  6. 运行结果:

    在这里插入图片描述


Alarm

该题的主要目的是让我们增加一个系统调用,该系统调用会在指定时间间隔后调用用户态的函数,因此需要我们对内核态的相关状态进行保存。按照实验提示一步一步来即可。涉及修改的文件如何所示:

在这里插入图片描述

  1. Makefile文件,增加如下代码:

    +       $U/_alarmtest\
    
  2. 文件kernel/proc.h文件

    struct proc {
       .... 
       struct file *ofile[NOFILE];  // Open files
       struct inode *cwd;           // Current directory
       char name[16];               // Process name 
    
       // begin+++++++ 
       int alarmticks; // ticks interval
       uint64 alarmhandler; // function to handler alarm
       int tickspass;
       struct trapframe * alarmtrapframe;
    
       int accessable;
       // end+++++++ 
    }
    
    
  3. 文件Kernel/proc.c,在函数allproc中增加如下内容:

    static struct proc*
    allocproc(void)
    {
      ......   
      // Allocate a trapframe page.
      // begin++++++
      if((p->alarmtrapframe = (struct trapframe *)kalloc()) == 0){
          release(&p->lock);
          return 0;
      }
    
      p->tickspass = 0;
      p->alarmticks = 0;
      p->alarmhandler = 0;
      p->accessable = 1;
    
      return p;
      // end++++++  
    }
    
  4. 文件Kernel/proc.c,在函数freeproc中增加如下内容:

     // begin++++ 
     if(p->alarmtrapframe)
          kfree((void*)p->alarmtrapframe);
      p->alarmtrapframe=0;
     // end+++++
    
  5. 在文件kernel/syscall.h中,增加如下内容

    // begin++++
    #define SYS_sigalarm 22
    #define SYS_sigreturn 23
    // end++++
    
  6. 在文件kernel/syscall.c中,增加如下内容

     ....
     extern uint64 sys_wait(void);
     extern uint64 sys_write(void);
     extern uint64 sys_uptime(void);
     // begin++++
     extern uint64 sys_sigalarm(void);
     extern uint64 sys_sigreturn(void);
     // end++++
    
     ...
    static uint64 (*syscalls[])(void) = {
     ....    
     //begin++++
     [SYS_sigalarm] sys_sigalarm,
     [SYS_sigreturn] sys_sigreturn,  
    }
     // end+++++
    
    
    
  7. 在文件kernel/syproc.c中,增加两个系统调用函数

    uint64
    sys_sigalarm(void)
    {
      int ticks = 0;
      uint64 alarmhandler = 0;
    
      if(argint(0, &ticks) < 0 || argaddr(1, &alarmhandler) < 0)
          return -1;
    
      struct proc *p = myproc();
      p->alarmticks = ticks;
      p->alarmhandler = alarmhandler;
      p->tickspass = 0;
      p->accessable = 1;
    
      return 0;
    }
    
    uint64
    sys_sigreturn(void)
    {
        struct proc *p = myproc();
        // 恢复系统调用时用户的栈帧
        if (0 == p->accessable) {
            memmove(p->trapframe, p->alarmtrapframe, sizeof(struct  trapframe));
            p->accessable = 1;
        }
    
        return 0;
    }
    
  8. 在kernel/trap.c文件的usertrap函数中,改为如下内容:

      // give up the CPU if this is a timer interrupt.
      if(which_dev == 2) {
          p->tickspass++;
          if (p->alarmticks != 0 && p->tickspass == p->alarmticks) {
              // return to user space to call the alarm function
              p->tickspass = 0;
              if (p->accessable == 1) {
                  memmove(p->alarmtrapframe, p->trapframe, sizeof(struct trapframe));
                  p->trapframe->epc = p->alarmhandler;
                  p->accessable = 0;
              }
          } else {
              yield();
          }
      }
    
  9. 在文件user/user.h中,添加如下内容:

    int sigalarm(int ticks, void (*handler)());
    int sigreturn(void);
    
  10. 在文件user/usys.pl中,添加如下内容:

    entry("sigalarm");
    entry("sigreturn");
    
  11. 查看结果

    在这里插入图片描述


提交结果

$ git commit -m "lab page tabls"
$ make handin

查看结果

登录网站https://6828.scripts.mit.edu/2020/handin.py/student,可以看到提交的结果。

在这里插入图片描述


课程涉及知识汇总

寄存器

  1. RISC-V有32个用户寄存器,用户可以使用全部的寄存器。
  2. 通过系统调用从用户态转到内核态时,相关系统调用的参数,可以通过a0至a7寄存器进行传递。通过调试我们发现:
    1. a7一般用来保存系统调用函数的编号;
    2. a0-a6可用来保存用户传递给系统调用的参数;
    3. a0也用来保存系统调用之后的返回结果;
  3. STVEC:在内核态的时候,指向的是内核的trap代码位置,在用户态的时候,应该指向用户的trap代码位置;
  4. SEPC: 内核的程序计算器
  5. 任何需要进行编译的语言(例如C语言),都不能修改用户寄存器。因此,用户寄存器必须在汇编代码中保存。
  6. SSTATUS:控制寄存器,本次课程中用到了其中的两个bit
    1. SPP:代表sret指令的行为,为0表示我们要进入用户态,为1表示我们要进入特权态();
    2. SPIE:代表是否打开中断,为0表示关闭,为1表示打开

page table

  1. trapframe page和trampoline page这两个页的PTE_U都为0,表示用户不能访问,只有进入supervisor mode才能访问;

指令

  1. ecall:该命令会将控制权由用户转到内核。执行该指令时,并不会切换page table

GDB

  1. tui enable可以打开对应的C语言代码;

Trapline

  1. 由内核映射在各个进程和内核空间的的代码;
  2. 该代码涉及到的功能有
    1. 将用户寄存器的值保存至trapframe对应字段;

Trapframe

  1. 保存用户态和内核态切换时的上下文信息,主要包含相关寄存器的值以及内核page table,内核stack的顶部指针,usertrap代码位置,程序计数器等相关信息,具体可参考文件的kernel/proc.h中的trapframe结构体。

常用命令

  1. ctrl + a c,进入qemu的console页面;
    1. 在console页面,输入info mem,可以打印当前的page table。

参考链接


Github

https://github.com/aerfalwl/mit-xv6-labs-2020.git

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值