6.S081 2020 lab4 trap 记录

RISC-V assembly (easy)

这部分没有代码实现,只需要查看对照call.c查看call.asm的内容,回答几个问题即可。

  1. Which registers contain arguments to functions? For example, which register holds 13 in main’s call to printf?
    函数参数通过寄存器a0-a7(整数类型,指针)和寄存器f0-f7(浮点数)传递。
    寄存器a2保存了main调用printf时的第三个参数的值13。
  2. Where is the call to function f in the assembly code for main? Where is the call to g? (Hint: the compiler may inline functions.)
    汇编代码中main没有对f进行调用,编译器在编译期已经直接计算出了结果12,在
    26: 45b1 li a1,12
    处将立即数12传入了a1。
    同样的对g也没有调用,在编译期直接计算好了结果。
  3. At what address is the function printf located?
    printf的地址在0x616。
  4. What value is in the register ra just after the jalr to printf in main?
    0x38
  5. Run the following code.
unsigned int i = 0x00646c72;
printf("H%x Wo%s", 57616, &i);

What is the output? Here’s an ASCII table that maps bytes to characters.
The output depends on that fact that the RISC-V is little-endian. If the RISC-V were instead big-endian what would you set i to in order to yield the same output? Would you need to change 57616 to a different value?

Here’s a description of little- and big-endian and a more whimsical description.

程序会输出 ‘HE110 World’
如果是大端序的机器,则需要将i设置为 0x726c6400,这是因为
0x72=‘r’
0x6c=‘l’
0x64=‘d’
0x00=‘\0’
57616不需要换成其他数字,不管在大端序还是小端序中,它的十六进制都是0xE110。

  1. In the following code, what is going to be printed after ‘y=’? (note: the answer is not a specific value.) Why does this happen?
printf("x=%d y=%d", 3);

y=后面输出的值取决于调用printf时寄存器a2的值。

Backtrace (moderate)

为了实现打调用栈上函数的地址的功能,需要用到寄存器fp的值,寄存器fp的值指向的是当前函数栈帧的栈底,实验指导已经提供了一个读取fp值的函数r_fp。将它放到riscv.h中。

// riscv.h

static inline uint64
r_fp()
{
  uint64 x;
  asm volatile("mv %0, s0" : "=r" (x) );
  return x;
}

从下图可以看到,对于每一个函数栈帧,地址(fp - 8)指向的是当前函数的返回值地址,也就是上一个函数的地址,这就是我们需要打印的东西。(fp - 16)处则是上一个栈帧的栈底的地址。所以读取当前fp的值后,可以类似于遍历链表那样遍历当前的调用栈。由于xv6中的栈大小只有一个page。所以最底层的栈帧的栈底不会超过PGROUNDUP(fp)

在这里插入图片描述
于是backtrace实现如下

// printf.c

void
backtrace(void)
{
  printf("backtrace:\n");
  uint64 fp = r_fp();
  uint64 limit = PGROUNDUP(fp);
  while (fp <= limit) {
    printf("%p\n", *(uint64 *)(fp - 8));
    fp = *(uint64 *)(fp - 16);
  }
}

然后将它的声明加入到def.h中,在sys_sleep函数中调用,这个实验就可以通过了。

// sys_proc.c

uint64
sys_sleep(void)
{
  int n;
  uint ticks0;
  // 随便找个地方调用,比如这里
  backtrace();

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

backtrace能够正常工作后,我们还可以将其将其放到panic中,这样xv6出现panic时就会打印出当前的调用链信息方便debug(虽然用gdb就可以了,不过实验指导部分让加上就加上吧)。

Alarm (hard)

这部分要求实现两个系统调用:sigalarmsigreturn。添加系统调用的过程这里不赘述。
sigalarm接受两个参数:interval和handler。用户进程调用sigalarm后,在进程执行时每发生interval个时间中断,执行流就应该跳转到handler去执行。
用户进程调用sigalarm时,sigalarm应该做两件事情,一是把自己的参数保存在某个地方,以便将来在发生interval次时钟中断后执行流能够找到handler,所以首先需要在struct proc结构体中添加对应的字段(增加的字段的初始化如无特殊说明都是在allocproc中被置0)。

// proc.h
struct sigalarm_param {
  int ticks;
  uint64 handler;
};

struct proc {
  // ...
  // these are private to the process, so p->lock need not be held.
  struct sigalarm_param sigalarm_param;
  int lefttick;
  struct trapframe sigreturn_context;
  int sigalarm_isrunning;
  // ...
};

在执行sys_sigalrm时,将参数保存到sigalarm_parm结构体中,同时将lefttick置为ticks,表示还有lefttick次时钟中断就该执行handler了。

// sysproc.c

uint64
sys_sigalarm(void)
{
  int ticks;
  uint64 handler;
  if (argint(0, &ticks) < 0) {
    return -1;
  }
  if (argaddr(1, &handler) < 0) {
    return -1;
  }
  struct proc *p = myproc();
  p->sigalarm_param.ticks = ticks;
  p->sigalarm_param.handler = handler;
  p->lefttick = ticks;
  return 0;
}

用户进程执行时,如果发生时间中断,执行流会进入到usertrap函数中,在这里判断发生trap的原因,如果是时钟中断,再让出CPU之前,需要判断此时是否该执行handler。要让进程在返回
用户态时执行handler,只需要将进程的trapframe中epc的值设为handler的地址即可。同时将lefttick重新设为最开始的值。为了让handleir在执行完后能够正确回到此时用户进程的地方,需要将当前的trapframe保存到sigreturn_context中。为了防止在handler返回前再被内核调用,设置p->sigalarm_isruning表示handler还在执行。

// trap.c
void
usertrap(void)
{
  // ...

  // give up the CPU if this is a timer interrupt.
  if(which_dev == 2){
    if (p->sigalarm_param.ticks != 0) {
      p->lefttick--;
      if (p->lefttick == 0 && p->sigalarm_isrunning == 0) {
        memmove(&p->sigreturn_context, p->trapframe,
                sizeof(p->sigreturn_context));
        p->trapframe->epc = p->sigalarm_param.handler;
        p->lefttick = p->sigalarm_param.ticks;
        p->sigalarm_isrunning = 1;
      }
    }
    yield();
  }

  usertrapret();
}

sigreturnhandler末尾被调用,它在内核中的实现如下:

uint64
sys_sigreturn(void)
{
  struct proc *p = myproc();
  memmove(p->trapframe, &p->sigreturn_context, sizeof(p->sigreturn_context));
  p->sigalarm_isrunning = 0;
  return 0;
}

它做的事情很简单,只是在返回用户态时将trapframe的状态恢复到执行handler之前的状态。并将sigalarm_isrunning置零表示handler执行完了。

测试一下效果,执行alarmtest
在这里插入图片描述
再执行下usertests
在这里插入图片描述
lab trap部分的实验就结束了。

  • 54
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值