6.S081/6.828: 4 Lab traps

2022-11-26 05:01:35 浏览数 (1)

这个实验探索系统调用是如何通过trap实现的,会涉及到汇编代码和寄存器操作,建议先参考xv6手册以及xv6源码分析--trap机制。

一、RISC-V assembly

1 Which registers contain arguments to functions? For example, which register holds 13 in main's call to printf?

系统调用号保存在a7,参数保存在a0--a6,一般syscall会返回一个uint64放入到a0中,至于其他参数会在内核态直接写入用户指定的位置。printf中的参数13会被放在a2中。

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。

image.pngimage.png

3 At what address is the function printf located?

auipc rd, immediate意义是rd=immetiate<<12 pc。

代码语言:c复制
void main(void) {
  1c:	1141                	addi	sp,sp,-16
  1e:	e406                	sd	ra,8(sp)
  20:	e022                	sd	s0,0(sp)
  22:	0800                	addi	s0,sp,16
  printf("%d %dn", f(8) 1, 13);
  24:	4635                	li	a2,13
  26:	45b1                	li	a1,12
  
  28:	00000517          	auipc	a0,0x0
  2c:	7b050513          	addi	a0,a0,1968 # 7d8 <malloc 0xea>
//将0x0左移12位 pc值=pc值,
  30:	00000097          	auipc	ra,0x0
  34:	600080e7          	jalr	1536(ra) # 630 <printf>
  exit(0);
  38:	4501                	li	a0,0
  3a:	00000097          	auipc	ra,0x0
  3e:	27e080e7          	jalr	638(ra) # 2b8 <exit>

进入main函数时各个寄存器状态如下:

image.pngimage.png
image.pngimage.png

然后执行完auipc和addi后,寄存器状态:

image.pngimage.png

最终jalr跳转到0x3ae位置,这就是printf syscall用户态地址。

image.pngimage.png

5 In the following code, what is going to be printed after 'y='?

5221.

image.pngimage.png

二、backtrace

1 问题分析

debugging时会有一个函数调用链,能够帮助我们发现在什么位置出现了什么error,实现这个功能,打印内核函数调用链。

  1. 通过stack frame pointer来打印调用链,gcc存储frame pointer在s0中。
  2. backtrace在kernel/printf.c中实现,并在sys_sleep中调用。
  3. xv6为每个进程分配了一页内核栈。
image.pngimage.png

2 代码实现

代码语言:c复制
void backtrace(void){
  
  printf("backtrace: n");
  uint64 fp=r_fp();
  uint64 top=PGROUNDUP(fp);
  uint64 bottom=PGROUNDDOWN(fp);
  while (fp<top && fp>bottom){
    uint64 last_fp=*(uint64*)(fp-16);
    uint64 ra=*(uint64*)(fp-8);
    printf("%pn",ra);
    fp=last_fp;
  }
}

三、alarm

1 问题分析

定时通知进程使用cpu的时间,这对一些需要限制cpu使用的进程有帮助。

  1. 增加系统调用sigalarm(interval, handler),每隔interval内核就调用一次handler。如果sigalarm(0, 0),内核就停止这个功能。
  2. interval、handler这两个应该存放到proc结构体中,并且还要追踪距离上次调用的时间间隔。
  3. cpu每隔一个tick,都会产生一个中断,在kernel/trap.c中由usertrap函数处理,会yield cpu,当前运行进程变为就绪态。

整个处理过程如图所示:

image.pngimage.png
  1. 时钟中断(which_dev=2)时更新current_tick_num,一旦达到interval,就将用户态pc设置为alarm_handler,并设置in_handler=1。
  2. 如果in_handler=1就不能yield,而是应该立刻返回用户态,此时返回的位置是alarm_handler(periodic)。
  3. alarm_handler执行结束后通过sigreturn系统调用重新回到内核态,将原本的状态(alarmframe)赋值给trapframe,最重要的是用户态pc,设置为原本应该返回的位置,并将in_handler=0,下次中断就会yield该进程cpu。

2 代码实现

2.1 sigalarm

我们需要先在user 目录下添加系统调用声明代码,然后在kernel中添加系统调用实现,会从用户态传递两个参数。我们需要将这两个参数放入到proc结构体来维护,并且维护一个字段来追踪距离上次调用alarm间隔。

代码语言:c复制
uint64 
sys_sigalarm(void){
  int ticks;
  uint64 handleraddr;
  if(argint(0, &ticks) < 0)
    return -1;
  if(argaddr(1, &handleraddr) < 0)
    return -1;
  struct proc *p=myproc();
  if(ticks==0||handleraddr==0){
    p->tick_interval=0;
    p->alarm_handler=0;
  }else{
    p->alarm_handler=(void (*)())handleraddr;
  }
  return 0;
}

//proc.c
struct proc {
  struct spinlock lock;

//...
  //for Lab 4,定时alarm
  int tick_interval; 
  void (*alarm_handler)(void);
  int current_tick_num;

 //...
};

每隔一个tick就会产生一个硬件时钟中断将进程yield出去,这个由usertrap处理,可以在这里判断是否需要执行进程的alarm_handler。alarm_handler是用户页表地址,内核页表没有对应页表项,所以需要切换到用户态去执行,结束后通过sigreturn进入到内核态。

代码语言:c复制
void
usertrap(void)
{
  int which_dev = 0;

  //...
  
  if(r_scause() == 8){
    // system call

    //...
  } else if((which_dev = devintr()) != 0){
    // ok
    // printf("which_dev: %d, p->in_handler: %dn",which_dev,p->in_handler);
    if(which_dev==2&&p->in_handler==0){
        p->current_tick_num  ;
      //时钟中断时决定是否执行alarm_handler,并且不能够重入
      if(p->current_tick_num>=p->tick_interval &&  p->tick_interval!=0){
          acquire(&p->lock);
          p->in_handler=1;
          p->current_tick_num=0;
          p->alarmframe=*p->trapframe;
          //返回用户态,handler位置,执行完sigreturn进内核
          //此次回到用户态是为了执行handler
          p->trapframe->epc=p->alarm_handler;
          release(&p->lock);
      }
    }
  } else {
    printf("usertrap(): unexpected scause %p pid=%dn", r_scause(), p->pid);
    printf("            sepc=%p stval=%pn", r_sepc(), r_stval());
    p->killed = 1;
  }

  if(p->killed)
    exit(-1);

  // give up the CPU if this is a timer interrupt.
    
  if(which_dev == 2){
    if(!p->in_handler){
      //printf("pid: %d inhandlern",p->pid);
      yield();
    }
      //如果in_handler=1,就直接返回用户态处理alarm_handler
  }

  usertrapret();
}



//sysproc.c

uint64 
sys_sigalarm(void){
  int ticks;
  uint64 handleraddr;
  if(argint(0, &ticks) < 0)
    return -1;
  if(argaddr(1, &handleraddr) < 0)
    return -1;
  struct proc *p=myproc();
  p->tick_interval=ticks;
  p->alarm_handler=handleraddr;
 
  // printf("sigalarm ,interval: %d, handler: %ldn",p->tick_interval,p->alarm_handler);
  return 0;
}

uint64
sys_sigreturn(void){
  // printf("sigreturn ,n");
  struct proc *p=myproc();
  acquire(&p->lock);
  *p->trapframe=p->alarmframe;
  p->in_handler=0;
  release(&p->lock);
  return 0;
}
image.pngimage.png

0 人点赞