Loading

MIT6.S081 ---- Lab traps

Lab traps

RISC-V assembly

阅读 call.asmf(),g() 函数,回答一些问题:

Ans

Backtrace

backtrace 对于 debug 很有用:在 error 发生时输出一列栈上的函数调用链。
编译器在每个 stack frame 中存放有一个 frame 指针(To Prev.Frame(fp)),这个 fp 指针指向调用者的 frame pointer 。本实验需要使用这些 fp 遍历 stack 区域然后打印每个 stack frame 保存的返回地址(return address)。

stack frames 的布局:

 --->Return Address
 |   To Prev.Frame(fp)
 |   Saved Registers
 |   Local Variables
 |   ...
 |   Return Address
 ----To Prev.Frame(fp)
     Saved Registers
     Local Variables
     ...

$sp 指向的是当前 stack frame 的底部(低地址)
$fp 指向的是当前 stack frame 的顶部(高地址)
当前 $fp - 8 的位置是 Return Address , $fp - 16 的位置是 To Prev.Frame(fp)。

void
backtrace(void)
{
  printf("backtrace:\n");

  uint64 fp = r_fp(); // top address of current stack frame
  uint64 stackpagetop = PGROUNDUP(fp); // top address of stack page

  while (fp < stackpagetop)
  {
    printf("%p\n", *(uint64 *)(fp - 8)); // Saved registers: $ra
    fp = *(uint64 *)(fp - 16); // Saved registers: To Prev.Frame
  }
}

Alarm

为xv6增加一个特性:定期向一个使用CPU时间的进程发出警报。对于想限制占用CPU时间的进程很有用,对于想进行计算又想执行周期任务的进程很有用。
更一般的说:实现一个原始的用户级 interrupt/fault handlers 。也可以使用类似的方法处理应用中的缺页。

需要增加一个新的 sigalarm(interval, handler) 系统调用。如果一个应用调用 sigalarm(n, fn),那么应用程序每消耗 \(n\) 个ticks,内核需要让应用程序执行函数 fn(test0)。当 fn 返回时,应用程序需要恢复 fn 函数执行之前的代码执行(test1/test2的实验目标)。tick 是 xv6 的时间单元,由硬件时钟产生中断决定。如果应用程序调用 sigalarm(0,0),内核需要停止生成周期时钟调用。

kernel/proc.h 中定义alarm相关属性。

  • histrapframe 用于在 \(n\) 个 ticks 后,执行 fn 前,在内核中保存用户态的用户寄存器,因为此次返回用户态并不是为了恢复正常代码的执行,而是为了执行 fn。在 fn 中必须有 sigreturn 系统调用再次进入内核,同时用 histrapframe 恢复原用户态的寄存器状态,从而在返回用户态时恢复正常执行。流程为:
    原流程代码 --> 调用sigalarm系统调用 -->
    进入内核设置alarm相关数据-->
    返回用户态执行原流程代码 -->
    ticks 到,因为时钟中断,所以此时在内核态,设置 $sepc 指向 fn,保存原 trapframe -->
    返回用户态,但是执行的是fn,而不是原流程代码 -->
    在 fn 中通过 sigreturn 再次进入内核态,恢复原流程代码的trapframe -->
    返回用户态执行原流程代码
    
  • alarmisreturn 用来标记 handler 是否返回,如果 handler 没有返回,则内核不能再次调用它。如 handler 的处理时间大于给定时间 \(n\) ticks 的时候:在用户态处理 handler 时,因为 ticks 到期再次 trap 进入内核,尽管时钟满足条件,但是 handler 没有返回,所以内核不应该再次调用 handler。
    分析下,实验这样限制的原因:当一个handler没有结束,再响应一个handler时,新的handler会污染原handler的PC和trapframe,造成程序执行异常。
  • alarminterval 表示周期数 \(n\)alarmpassed 表示当前经过的时间, (*alarmhandler)() 表示fn的函数指针。
int alarminterval;               // sigalarm alarm interval
int alarmpassed;                 // sigalarm passed time
void (*alarmhandler)();          // sigalarm handler
struct trapframe histrapframe;   // save trapframe to retrieve to original code from alarm handler.
int alarmisreturn;               // 0 is not return so that can not re-enter handler. 1 can do.

kernel/sysproc.c:取出系统调用的参数,配置 proc 的 alarm 相关属性。

// After every n "ticks" of CPU time that the program consumes,
// the kernel should call fn.
uint64
sys_sigalarm(void)
{
  int n;
  uint64 addr;

  if (argint(0, &n) < 0 || argaddr(1, &addr) < 0)
    return -1;

  myproc()->alarminterval = n;
  myproc()->alarmpassed = 0;
  myproc()->alarmisreturn = 1;
  myproc()->alarmhandler = (void(*)())addr;

  return 0;
}

uint64
sys_sigreturn(void)
{
  *(myproc()->trapframe) = myproc()->histrapframe;
  myproc()->alarmpassed = 0;
  myproc()->alarmisreturn = 1;
  return 0;
}

kernel/trap.c:判断是否应该执行 alarm handler(时钟周期到且上一个alarm handler已经返回)。保存 trapframe 以便 sigreturn 中返回,记录当前 alarm handler 没有返回,防止后续 handler 的“覆盖“执行。

// give up the CPU if this is a timer interrupt.
  if (which_dev == 2) {
    if (p->alarminterval > 0) {
      p->alarmpassed++;
      if (p->alarmisreturn && p->alarmpassed >= p->alarminterval) {
        p->histrapframe = *(p->trapframe);
        p->trapframe->epc = (uint64)p->alarmhandler;
        p->alarmisreturn = 0;
      }
    }

Code

Code:Lab traps

posted @ 2022-01-20 17:16  seaupnice  阅读(186)  评论(0编辑  收藏  举报