# Backtrace

大意是需要跟踪函数调用轨迹,根据栈的结构:

可以得出,用户态函数调用栈从高地址向低地址增长。栈帧指针 fp 向下 8 个字节是返回地址,向下 16 个字节指向调用者的栈。只要顺着调用者的栈不断向下查找就能输出完整的调用轨迹。

由于每个栈分配一个对齐的页,所以通过判断 fp 在不在当前页范围内就能知道有没有追踪到底。

void
backtrace(void){
    printf("backtrace:\n");
    uint64 fp=r_fp();
    uint64 up = PGROUNDUP(fp);
    uint64 down = PGROUNDDOWN(fp);
    while(fp<up && fp>down) {
        printf("%p\n", *((uint64 *) (fp - 8)));// return address
        fp = *((uint64 *) (fp - 16));//last fp
    }
}

# Alarm

简而言之,要实现一个定时输出 “alarm” 的程序,每隔指定的时钟中断周期就调用一次指定的函数(测试程序为输出 “alarm”)。

根据 hints,将 alarmtest 加入 makefile,相关的函数 sigalarm 和 sigreturn 加入到 usys.pl

entry("sigalarm");
entry("sigreturn");

在 syscall.c 中更新 syscall 调用表:

static uint64 (*syscalls[])(void) = {
    //...
[SYS_sigalarm]   sys_sigalarm,
[SYS_sigreturn]   sys_sigreturn,
};

至此,已经可以通过命令行调用 alarmtest 函数。

接下来为了实现该功能,要在 proc 结构体中新增一些变量,打开 proc.h。

struct proc {
  // ...
  //for alarm
  int ticks;// 多少个时钟周期触发
  int ticks_pass; // 已经经过的时钟周期数
  uint64 handler; // 要调用的处理函数
  struct alarm_context alarm_context; // 保存的寄存器
  int alarm_status; // 当前是否在 alarm 处理中
};

为了在调用 alarm 之后,能恢复原来的程序,要保存的寄存器如下,保守起见,除了 t 系列寄存器其它都保存了。注意 alarm_context 在 proc 结构体中不要使用指针,否则需要分配空间,较为繁琐,若不分配会 panic: kernel trap。

struct alarm_context{
    /*  24 */ uint64 epc;           // saved user program counter
    /*  40 */ uint64 ra;
    /*  48 */ uint64 sp;
    /*  56 */ uint64 gp;
    /*  64 */ uint64 tp;
    /*  96 */ uint64 s0;
    /* 104 */ uint64 s1;
    /* 112 */ uint64 a0;
    /* 120 */ uint64 a1;
    /* 128 */ uint64 a2;
    /* 136 */ uint64 a3;
    /* 144 */ uint64 a4;
    /* 152 */ uint64 a5;
    /* 160 */ uint64 a6;
    /* 168 */ uint64 a7;
    /* 176 */ uint64 s2;
    /* 184 */ uint64 s3;
    /* 192 */ uint64 s4;
    /* 200 */ uint64 s5;
    /* 208 */ uint64 s6;
    /* 216 */ uint64 s7;
    /* 224 */ uint64 s8;
    /* 232 */ uint64 s9;
    /* 240 */ uint64 s10;
    /* 248 */ uint64 s11;
};

然后更改 usertrap 函数,在时钟中断中加入处理逻辑。

void
usertrap(void)
{
  // ...
  // give up the CPU if this is a timer interrupt.
  if(which_dev == 2){
      if(p->ticks>0){
          p->ticks_pass++;
          if(p->ticks_pass>=p->ticks&&!(p->alarm_status)){
              p->alarm_status=1; // 当前正在处理 alarm, 不响应其它 alarm
              p->ticks_pass=0;
              alarm_store();// 保存寄存器到当前 proc
              p->trapframe->epc=p->handler; // 指定的处理函数
          }
      }
      yield();
  }
  usertrapret();
}

alarm_store 就是复制寄存器,无脑。

void
alarm_store(void)
{
    struct proc *p = myproc();
    p->alarm_context.epc=p->trapframe->epc;
    p->alarm_context.a0=p->trapframe->a0;
    p->alarm_context.a1=p->trapframe->a1;
    p->alarm_context.a2=p->trapframe->a2;
    p->alarm_context.a3=p->trapframe->a3;
    p->alarm_context.a4=p->trapframe->a4;
    p->alarm_context.a5=p->trapframe->a5;
    p->alarm_context.a6=p->trapframe->a6;
    p->alarm_context.a7=p->trapframe->a7;
    p->alarm_context.sp=p->trapframe->sp;
    p->alarm_context.s0=p->trapframe->s0;
    p->alarm_context.s1=p->trapframe->s1;
    p->alarm_context.s2=p->trapframe->s2;
    p->alarm_context.s3=p->trapframe->s3;
    p->alarm_context.s4=p->trapframe->s4;
    p->alarm_context.s5=p->trapframe->s5;
    p->alarm_context.s6=p->trapframe->s6;
    p->alarm_context.s7=p->trapframe->s7;
    p->alarm_context.s8=p->trapframe->s8;
    p->alarm_context.s9=p->trapframe->s9;
    p->alarm_context.s10=p->trapframe->s10;
    p->alarm_context.s11=p->trapframe->s11;
    p->alarm_context.gp=p->trapframe->gp;
    p->alarm_context.ra=p->trapframe->ra;
    p->alarm_context.tp=p->trapframe->tp;
}

初始化 proc 也要增加一些字段:

static struct proc*
allocproc(void)
{
 //...
  //init alarm
  memset(&p->alarm_context,0,sizeof(p->alarm_context));
  p->ticks=0;
  p->alarm_status=0;
  p->ticks_pass=0;
  p->handler=0;
  return p;
}

最后是新增加的两个函数,写在 sysproc.c 中:

uint64
sys_sigalarm(void){// 主要为了获取参数
    int ticks;
    uint64 handler;
    argint(0,&ticks);
    argaddr(1,&handler);
    struct proc*p=myproc();
    p->ticks=ticks;
    p->handler=handler;
    p->ticks_pass=0;
    return 0;
}
uint64
sys_sigreturn(void){// 主要为了恢复现场
    struct proc*p=myproc();
    p->trapframe->epc=p->alarm_context.epc;
    p->trapframe->a0=p->alarm_context.a0;
    p->trapframe->a1=p->alarm_context.a1;
    p->trapframe->a2=p->alarm_context.a2;
    p->trapframe->a3=p->alarm_context.a3;
    p->trapframe->a4=p->alarm_context.a4;
    p->trapframe->a5=p->alarm_context.a5;
    p->trapframe->a6=p->alarm_context.a6;
    p->trapframe->a7=p->alarm_context.a7;
    p->trapframe->sp=p->alarm_context.sp;
    p->trapframe->s0=p->alarm_context.s0;
    p->trapframe->s1=p->alarm_context.s1;
    p->trapframe->s2=p->alarm_context.s2;
    p->trapframe->s3=p->alarm_context.s3;
    p->trapframe->s4=p->alarm_context.s4;
    p->trapframe->s5=p->alarm_context.s5;
    p->trapframe->s6=p->alarm_context.s6;
    p->trapframe->s7=p->alarm_context.s7;
    p->trapframe->s8=p->alarm_context.s8;
    p->trapframe->s9=p->alarm_context.s9;
    p->trapframe->s10=p->alarm_context.s10;
    p->trapframe->s11=p->alarm_context.s11;
    p->trapframe->gp=p->alarm_context.gp;
    p->trapframe->ra=p->alarm_context.ra;
    p->trapframe->tp=p->alarm_context.tp;
    p->alarm_status=0;
    return p->alarm_context.a0;// 返回保存的 a0 寄存器,因为之前的返回值放在 a0 中,而其它中断返回值会覆盖 a0
}

为什么需要设置 alarm 状态,判断是否在 alarm 中?因为处理中断要花费的时钟周期可能比指定触发间隔更长,为避免无限嵌套中断,需要拒绝执行正在 alarm 状态中收到的 alarm 中断请求。

# Test

更新于