s081-2020 Lab4 traps

Lab4 traps

在这里插入图片描述

RISC-V assembly (easy)

将问题的答案添加到answers-traps.txt

Q: Which registers contain arguments to functions? For example, which register holds 13 in main’s call to printf?
A: a0-a7; a2;

Q: 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.)
A: There is none. g(x) is inlined within f(x) and f(x) is further inlined into main()

Q: At what address is the function printf located?
A: 0x0000000000000628, main calls it with pc-relative addressing.

Q: What value is in the register ra just after the jalr to printf in main?
A: 0x0000000000000038, next line of assembly right after the jalr

Q: Run the following code.

unsigned int i = 0x00646c72;
printf(“H%x Wo%s”, 57616, &i);

What is the output?
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?
A: “He110 World”; 0x726c6400; no, 57616 is 110 in hex regardless of endianness.

Q: 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);

A: A random value depending on what codes there are right before the call.Because printf tried to read more arguments than supplied.
The second argument 3 is passed in a1, and the register for the third argument, a2, is not set to any specific value before the
call, and contains whatever there is before the call.

Backtrace (moderate)

这个task要求我们在kernel/print.c添加backtrace()函数,用来打印内核调用栈信息(return address);hints中给了r_fp()用来获取当前的frame point:

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

下图是risc-v体系结构下,c语言程序的调用栈布局:

在这里插入图片描述

由于return address以及prev Frame都在栈帧的固定位置,分别是fp+4, fp+8(fpuint64类型),因此我们可以不断遍历整个内核调用栈打印出每个return address;另外一个要点是,要小心遍历过程中超出内核栈的页面范围导致panic,这要求我们在遍历过程中fp始终在同一个页面中:

// kernel/print.c  加到defs.h

void backtrace() {
  uint64 fp = r_fp();
  // 内核栈的页面基址
  uint64 pgbase = PGROUNDDOWN((uint64)fp);
  while (PGROUNDDOWN((uint64)fp) == pgbase)  // 必须位于同一个页面中
  {
    printf("%p\n", *(uint64*)(fp - 8));  // 打印return address
    fp = *(uint64*)(fp - 16);            // point to prev frame
  }
}

然后在sys_sleep中调用:

// kernel/sysproc.c

uint64
sys_sleep(void)
{

  backtrace();
  int n;
  uint ticks0;

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PYjkCssG-1638933400956)(/Users/liuzhilei/workspace-git/study-repo/6.S081/lab/traps.assets/image-20211207232242612.png)]

上图是执行结果,在另一个终端的项目目录下执行riscv64-unknown-elf-addr2line -e kernel/kernel,并粘贴上图输出的return address就能打印出对应的调用函数:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vqX0m1BH-1638933400957)(/Users/liuzhilei/workspace-git/study-repo/6.S081/lab/traps.assets/image-20211207232444306.png)]

Alarm (hard)

这个task的要实现一个时钟中断处理函数,当时钟中断达到一定次数后执行用户态的回调函数。这里的一个难点就是回调函数在用户态,不能在内核中直接调用;另外回调函数不在进程的原始执行序列中,因此要执行回调函数必须在内核态手动地改变指令流;由于执行了回调函数,回调函数会修改寄存器状态即改变了进程状态;因此必须在回调函数执行前保存进程状态,在回调函数执行完成后恢复进程状态;

userver将寄存器状态暂存在trapframe中,而userrettrapframe恢复到寄存器中;因此为了保护进程状态,我们只需要在时钟中断处理函数sys_sigalrm中暂存trapframe,并在sys_sigreturn中恢复trapframe;

另一点需要注意的是,我们必须避免在回调函数执行过程中再次执行回调函数,这样会破坏我们暂存的trapframe;为此我们需要记录当前进程是否在执行回调函数,如果是则不再执行回调函数即可。

为struct proc增加相应的字段:

struct proc{
	...
  int pending;             // 不为0表示当前进程已经处于alarm回调处理过程
  int intervals;           // intervals > 0 表明开启了alarm回调
  int tickers;             // 已经经过的tickers
  void(* handler)();       // 回调函数,位于用户态
  struct trapframe *alarm_trapframe;  // 执行alarm handler时用来保存进程的trapfram
};

sigalarmsigreturn 具体实现:

// kernel/sysproc.c
uint64 sys_sigalarm(void)
{
  int n;
  uint64 fn;
  if (argint(0, &n) < 0)
    return -1;
  if (argaddr(1, &fn) < 0)
    return -1;

  struct proc* p = myproc();
  p->intervals = n;
  p->tickers = n;
  p->handler = (void(*)())(fn);
  return 0;
}

uint64 sys_sigreturn(void)
{
  struct proc* p = myproc();
  *p->trapframe = *p->alarm_trapframe;// 这里必须是值拷贝
  p->pending = 0;
  return 0;
}

allocproc()中初始化相应字段,并分配alarm_trapframe的页面;相应地在freecproc()中还原字段的值,并销毁alarm_trapframe的页面:

// kernel/proc.c

static struct proc*
allocproc(void)
{
  ...

found:
  p->pid = allocpid();

  // Allocate a trapframe page.
  if((p->trapframe = (struct trapframe *)kalloc()) == 0){
    release(&p->lock);
    return 0;
  }

    // Allocate a trapframe page for alarm_trapframe.
  if((p->alarm_trapframe = (struct trapframe *)kalloc()) == 0){
    release(&p->lock);
    return 0;
  }

  p->intervals = 0;
  p->tickers = 0;
  p->handler = 0;
  p->pending = 0;

  // An empty user page table.
  p->pagetable = proc_pagetable(p);
  if(p->pagetable == 0){
    freeproc(p);
    release(&p->lock);
    return 0;
  }
  ...
}

static void
freeproc(struct proc *p)
{
  ...

  // 释放分配给alarm_trapfram的页面
  if(p->alarm_trapframe)
    kfree((void*)p->alarm_trapframe);
  p->alarm_trapframe = 0;
  // 清空alarm中断处理相关字段
  p->intervals = 0;
  p->pending = 0;
  p->tickers = 0;
  p->handler = 0;

  p->state = UNUSED;
}

usertrap() 函数中,为时钟中断增加alarm的回调机制:

 // kernel/trap.c
 
 void
usertrap(void)
{
	...
 	// give up the CPU if this is a timer interrupt.
  if (which_dev == 2) {
     // 设置了alarm中断处理
     // 如果上一个回调函数还没有执行完毕,就不再处理新的中断
    if (p->intervals > 0 && p->pending == 0)
    {
        p->tickers -= 1;
        if (p->tickers <= 0)
        {
          p->tickers = p->intervals;
          // 暂存进程的trapfram,用于在中断处理结束后恢复
          *p->alarm_trapframe = *p->trapframe;  // 这里必须是值拷贝
          p->trapframe->epc = (uint64)p->handler; // 返回用户态时执行中断处理函数
          p->pending = 1;
        }
    }
    yield();
  }
  usertrapret();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值