【操作系统】MIT 6.s081 LAB4

LAB 4: Traps

原文地址:YSBLOG
参考:[mit6.s081] 笔记 Lab4: Traps | 中断陷阱

实验目的:探索如何通过trap实现系统调用。

RISC-V assembly (easy)

阅读call.asm函数代码,回答一下问题:

1、哪些寄存器保存函数的参数?例如,在mainprintf的调用中,哪个寄存器保存13?

在risc-v中a0-a7寄存器保存函数参数,如果函数参数超过8个,则保存在内存中,函数调用的默认第一个参数为函数本身名字,存放在a0寄存器,所以13作为printf的第二个参数存放在a2寄存器。

2、main的汇编代码中对函数f的调用在哪里?对g的调用在哪里(提示:编译器可能会将函数内联)

在call.asm第45行可以看出(li a1, 12)main中直接把f(8)+1的值传递给了a1寄存器,所以在汇编中main函数进行了内联优化处理,在mian的汇编代码中并有没对f进行函数调用。

3、printf函数位于哪个地址?

在call.asm第50行可以看出,printf的地址在0x640

4、在mainprintfjalr之后的寄存器ra中有什么值?

ra寄存器用来保存函数执行以后的下一个执行指令的地址,printf的jalr之后,ra寄存器应当是返回main函数的地址,为0x638

5、运行以下代码。

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

程序的输出是什么?这是将字节映射到字符的ASCII码表。

程序的输出为"Hello World"

输出取决于RISC-V小端存储的事实。如果RISC-V是大端存储,为了得到相同的输出,你会把i设置成什么?是否需要将57616更改为其他值?

如果RISC-V是大端存储,为了得到相同的输出,需要把i设置成0x726c6400,不需要把57616修改为其他值。

在下面的代码中,“y=”之后将打印什么(注:答案不是一个特定的值)?为什么会发生这种情况?

printf("x=%d y=%d", 3);

y=%d中的%d会替换为当前a2寄存器中的值,因为当前a2寄存器中值不明确,所以答案不是一个特定的值

Backtrace(moderate)

该任务的目的是输出调用backtrace()函数的cpu的函数当前函数调用关系。

【操作系统】MIT 6.s081 LAB4_第1张图片

根据课程PPT中的结构图可以知道,fp指向当前函数栈的开头,sp执行当前函数栈的结尾。

每一个函数的前8位表示当前函数结束后返回上一级的地址(ra),8-16位表示上一级函数栈的fp,由于整个栈是由高地址向低地址扩展,所以uint64 ra = *(uint64*)(fp - 8); fp = *(uint64*)(fp - 16);

所以我们想要实现输出当前cpu所有函数栈,我们只需要通过获取当前fp寄存器的值后不断回到上一级fp,直到fp与当前页面顶部地址相同,则说明回到了第一级的fp。

1、在defs.h中添加函数定义`

void            backtrace(void);

2、根据提示,在riscv.h中添加获取当前fp寄存器的方法

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

3、在printf.c中实现backtrace()

void
backtrace(void) {
  printf("backtrace:\n");
  // 读取当前帧指针
  uint64 fp = r_fp();
  while (fp != PGROUNDUP(fp)) {
    // xv6中,用一个页来存储栈,如果fp已经达到了栈页的上届,说明已经达到栈底
    // 地址扩张是向低地址扩展,所以当fp到达最高地址时说明到达栈底
    uint64 ra = *(uint64*)(fp - 8); // return address
    printf("%p\n", ra);
    fp = *(uint64*)(fp - 16); // preivous fp
  }
}

4、在printf.cpainc中添加backtrack的调用,这样就能在发生异常时定位问题了

void
panic(char *s)
{
  backtrace();
  pr.locking = 0;
  printf("panic: ");
  printf(s);
  printf("\n");
  panicked = 1; // freeze uart output from other CPUs
  for(;;)
    ;
}

实验结果如下:

【操作系统】MIT 6.s081 LAB4_第2张图片

Alarm(Hard)

该任务的目的是通过定时器中断实现定期警报功能,经过一定的CPU时间后,执行指定报警函数。

该任务的test0是完成对于报警函数的调用,不考虑函数的返回,在test1,test2中需要考虑在执行完报警函数后,能够正确返回被中断之前的状态(让报警函数对被中断的函数透明),所以需要保存中断之前的所有寄存器。

这里将全部要求一起实现,具体过程如下:

1、在makefile中添加 $U/_alarmtest\

2、在usys.pl中添加entry("sigalarm");``entry("sigreturn");

3、在syscall.h中添加#define SYS_sigalarm 22 *#define* SYS_sigreturn 23

4、在syscall.c中添加extern uint64 sys_sigalarm(void); extern uint64 sys_sigreturn(void);

5、在syscall.c中添加[SYS_sigalarm] sys_sigalarm,[SYS_sigreturn] sys_sigreturn,

6、proc.h中添加

struct proc {
    ... ...     
  uint64 interval;              // alarm interval time
  void (*handler)();            // alarm handle function
  uint64 ticks;                 // how many ticks have passed since the last call
  struct trapframe *alarm_trapframe;  // A copy of trapframe right before running alarm_handler
  int alarm_goingoff;          // Is an alarm currently going off and hasn't not yet returned? (prevent re-entrance of alarm_handler)
}

7、在proc.callocproc函数,初始化新增字段

static struct proc*
allocproc(void)
{
 ... ...
  // Allocate a trapframe page.
  if((p->trapframe = (struct trapframe *)kalloc()) == 0){
    release(&p->lock);
    return 0;
  }
  if((p->alarm_trapframe = (struct trapframe *)kalloc()) == 0){
    release(&p->lock);
    return 0;
  }
... ...
  p->ticks = 0;
  p->handler = 0;
  p->interval = 0;
  p->alarm_goingoff = 0;
  return p;
}

8、在proc.cfreeproc函数中释放新增字段

static void
freeproc(struct proc *p)
{
  if(p->trapframe)
    kfree((void*)p->trapframe);
  if (p->alarm_trapframe) {
    kfree((void*)p->alarm_trapframe);
  }
... ...
  p->ticks = 0;
  p->handler = 0;
  p->interval = 0;
  p->alarm_goingoff = 0;
  p->state = UNUSED;
}

9、实现sys_sigalarm()sig_sigreturn()函数,用于实现系统调用

uint64
sys_sigalarm(void) {
  // sigalarm 的第一个参数为ticks,第二个参数为void(*handler)()
  int n;
  uint64 handler;
  if (argint(0, &n) < 0) {
    return -1;
  }
  if (argaddr(1, &handler) < 0) {
    return -1;
  }
  return sigalarm(n, (void(*)())(handler));
}
uint64
sys_sigreturn(void) {
  return sigreturn();
}

10、在trap.c中实现int sigalarm(int ticks, void(*handler)()以及int sigreturn()

int sigalarm(int ticks, void(*handler)()) {
    // 初始化alarm时设置该进程的计数大小以及对于alarm函数
  struct proc *p = myproc();
  p->interval = ticks;
  p->handler = handler;
  p->ticks = 0;
  return 0; 
}
int sigreturn() {
    // alarm返回时将备份的trapframe寄存器恢复,确保回退时cpu状态和进入中断时一致,对被中断函数透明
  struct proc *p = myproc();
  *(p->trapframe) = *(p->alarm_trapframe);
    // 清除进入alarm标志位,确保能再次进入
  p->alarm_goingoff = 0;
  return 0;
}

11、在trap.cusertrap函数中添加定时器中断响应

void
usertrap(void)
{
  int which_dev = 0;

  if((r_sstatus() & SSTATUS_SPP) != 0)
    panic("usertrap: not from user mode");

  // send interrupts and exceptions to kerneltrap(),
  // since we're now in the kernel.
  w_stvec((uint64)kernelvec);

  struct proc *p = myproc();
  
  // save user program counter.
  p->trapframe->epc = r_sepc();
  
  if(r_scause() == 8){
    // system call

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

    // sepc points to the ecall instruction,
    // but we want to return to the next instruction.
    p->trapframe->epc += 4;

    // an interrupt will change sstatus &c registers,
    // so don't enable until done with those registers.
    intr_on();

    syscall();
  } else if((which_dev = devintr()) != 0){
    // ok
    if (which_dev == 2) {
        // 当which_dev为2时,表示每个CPU时间触发的中断
      if (p->interval != 0) { // 设定了时钟条件
        if (p->ticks == p->interval && p->alarm_goingoff == 0) {
          // 达到计时次数后
          // 此时处于内核中断
          p->ticks = 0;
          // A程序进入内核时,会将pc的值存到spec中,离开内核时再从spec中读取该值,返回A中代码执行的地方
          // 此时修改spec寄存器的值为目标函数,就能够在离开中断时返回到我们想要的地方
          // 但在次之前要保存当前进程的寄存器值,保证在推出目标handler时能够回到A程序正确的位置中
          *(p->alarm_trapframe) = *(p->trapframe);
          p->trapframe->epc = (uint64)p->handler;
          p->alarm_goingoff = 1; // 不允许递归触发handler
        }
        p->ticks++; // cpu每产生一次timer中断计数++
      }
    }
  } else {
    printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid);
    printf("            sepc=%p stval=%p\n", 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)
    yield();

  usertrapret();
}

实验结果:

【操作系统】MIT 6.s081 LAB4_第3张图片

你可能感兴趣的:(操作系统,C/C++,c语言,操作系统,mit)