6.S081 Lab4 Traps

第一部分 RISC-V assembly 阅读汇编

相关的C代码:

#include "kernel/param.h"
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

int g(int x) {
  return x+3;
}

int f(int x) {
  return g(x);
}

void main(void) {
  printf("%d %d\n", f(8)+1, 13);
  exit(0);
}

相关的汇编代码:

0000000000000000 <g>:
#include "kernel/param.h"
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

int g(int x) {
   0:	1141                	addi	sp,sp,-16
   2:	e422                	sd	s0,8(sp)
   4:	0800                	addi	s0,sp,16
  return x+3;
}
   6:	250d                	addiw	a0,a0,3
   8:	6422                	ld	s0,8(sp)
   a:	0141                	addi	sp,sp,16
   c:	8082                	ret

000000000000000e <f>:

int f(int x) {
   e:	1141                	addi	sp,sp,-16
  10:	e422                	sd	s0,8(sp)
  12:	0800                	addi	s0,sp,16
  return g(x);
}
  14:	250d                	addiw	a0,a0,3
  16:	6422                	ld	s0,8(sp)
  18:	0141                	addi	sp,sp,16
  1a:	8082                	ret

000000000000001c <main>:

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 %d\n", 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>
  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>

问题:

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

24: 4635 li a2,13可以看出来13存储在a2寄存器,相应地,前两个参数分别寄存在a0和a1。

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.)

编译器把函数优化成inline的,主函数中对f的调用直接被优化成了li a1,12

At what address is the function printf located?

jalr	1536(ra) # 630 <printf>

0000000000000630 <printf>:

Run the following code.
What is the output? Here’s an ASCII table that maps bytes to characters.

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

57616的16进制是e110

ascii码对照:

2进制 10进制 16进制 字符
0111 0010 114 72 r
0110 1100 108 6c l
0110 0100 100 64 d

综上,打印结果为He110 World

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

%d的取值取决于a2寄存器的值,如果没有设置,该值可能是随机的。


第二部分 Backtrace 回溯

大概意思是回溯打印函数调用栈中的返回地址(即发生函数调用的地址)

Implement a backtrace() function in kernel/printf.c. Insert a call to this function in sys_sleep, and then run bttest, which calls sys_sleep. Your output should be as follows:

backtrace:
0x0000000080002cda
0x0000000080002bb6
0x0000000080002898

After bttest exit qemu. In your terminal: the addresses may be slightly different but if you run addr2line -e kernel/kernel (or riscv64-unknown-elf-addr2line -e kernel/kernel) and cut-and-paste the above addresses as follows:

$ addr2line -e kernel/kernel
0x0000000080002de2
0x0000000080002f4a
0x0000000080002bfc
Ctrl-D

You should see something like this:

kernel/sysproc.c:74
kernel/syscall.c:224
kernel/trap.c:85

上代码,首先在risv.h中添加从寄存器中读取当前fp指针的函数

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

printf.c中添加backtrace函数

void backtrace(void)
{
  uint64 fp = r_fp();
  uint64 upbound = PGROUNDUP(fp);

  while (fp < upbound) {
    printf("%p\n", *(uint64*)(fp - 8));
    fp = *(uint64*)(fp - 16);
  }

}

之后在defs.h添加函数声明。最后在sys_sleep函数(sysproc.c)中调用backtrace函数。即可编译运行测试。

hart 1 starting
hart 2 starting
init: starting sh
$ bttest
0x00000000800020cc
0x0000000080001fa6
0x0000000080001c90

第三部分 Alarm (hard)

In this exercise you’ll add a feature to xv6 that periodically alerts a process as it uses CPU time. This might be useful for compute-bound processes that want to limit how much CPU time they chew up, or for processes that want to compute but also want to take some periodic action. More generally, you’ll be implementing a primitive form of user-level interrupt/fault handlers; you could use something similar to handle page faults in the application, for example. Your solution is correct if it passes alarmtest and usertests.

首先编写系统调用

uint64 sys_sigalarm(void) {
  int interval;
  if (argint(0, &interval) < 0) {
    return -1;
  }
  
  void (*fn) (void);

  if (argaddr(1, (uint64*)&fn) < 0) {
    return -1;
  }
  struct proc *p = myproc();
	
  // 如果传入两个0,清空
  if (interval == 0 && fn == 0) {
    p->interval = -1;
    p->tiktok = 0;
    return 0;
  }

  // 保存间隔时间,保存函数指针,计时为0
  p->interval = interval;
  p->fn = fn;
  p->tiktok = 0;

  return 0;
}

// 恢复系统调用,恢复之前保存下来的寄存器(在trap.c中保存的)
uint64 sys_sigreturn(void) {
  struct proc *p = myproc();
  p->trapframe->epc = p->pc2;
  p->trapframe->s0 = p->s0_2;
  p->trapframe->ra = p->ra;
  p->trapframe->sp = p->sp;
  p->flag = 0; // 清空flag,指示当前可以重入handler函数

  p->trapframe->a0 = p->a0;
  p->trapframe->a1 = p->a1;
  p->trapframe->a2 = p->a2;
  p->trapframe->a3 = p->a3;
  ···

  // printf("%p\n", p->trapframe->epc);
  return 0;
};

修改proc.h中的结构体定义

// Per-process state
struct proc {
  struct spinlock lock;

  // p->lock must be held when using these:
  enum procstate state;        // Process state
  void *chan;                  // If non-zero, sleeping on chan
  int killed;                  // If non-zero, have been killed
  int xstate;                  // Exit status to be returned to parent's wait
  int pid;                     // Process ID

  // wait_lock must be held when using this:
  struct proc *parent;         // Parent process

  // these are private to the process, so p->lock need not be held.
  uint64 kstack;               // Virtual address of kernel stack
  uint64 sz;                   // Size of process memory (bytes)
  pagetable_t pagetable;       // User page table
  struct trapframe *trapframe; // data page for trampoline.S
  struct context context;      // swtch() here to run process
  struct file *ofile[NOFILE];  // Open files
  struct inode *cwd;           // Current directory
  char name[16];               // Process name (debugging)

  // 需要保持的寄存器
  int interval;
  int tiktok;
  uint64 pc2;
  uint64 s0_2;
  uint64 sp;
  uint64 ra;
  uint64 a0;
  uint64 a1;
  uint64 a2;
  uint64 a3;
  ···

  // 需要新增的字段
  int flag; // 是否正在处理handler函数
  void (*fn)(void); // handler函数指针
};

修改trap.c

void
usertrap(void)
{
  ···
  // give up the CPU if this is a timer interrupt. 如果是tick中断
  if(which_dev == 2) {
    if (p->interval != -1) { // 如果计时器激活
      p->tiktok += 1; // 计时加1
    }
    yield();
  }

  usertrapret();
}

//
// return to user space
//
void
usertrapret(void)
{
  struct proc *p = myproc();

  ···
  // set S Exception Program Counter to the saved user pc.
  // 如果计时器到时,且flag为0(flag指示当时是否在handler过程中)
  if (p->tiktok == p->interval && p->flag != 1) {
  // printf("%p %p\n", p->fn, p->trapframe->ra);
  // printf("%p %p\n", p->trapframe->epc, p->trapframe->ra);
  // 保存寄存器
    p->pc2 = p->trapframe->epc;
    p->s0_2 = p->trapframe->s0;
    p->ra = p->trapframe->ra;
    p->sp = p->trapframe->sp;
    p->a0 = p->trapframe->a0;
    p->a1 = p->trapframe->a1;
    p->a2 = p->trapframe->a2;
    p->a3 = p->trapframe->a3;
    ···

    p->flag = 1;

    // printf("%p\n", p->trapframe->epc);
    // printf("%p\n", p->pc2);
    // 将epc指向fn函数的起始地址
    p->trapframe->epc = (uint64)(p->fn);
    // tiktok置零
    p->tiktok = 0;
  }

  w_sepc(p->trapframe->epc);

  // tell trampoline.S the user page table to switch to.
  uint64 satp = MAKE_SATP(p->pagetable);

  // jump to trampoline.S at the top of memory, which 
  // switches to the user page table, restores user registers,
  // and switches to user mode with sret.
  uint64 fn = TRAMPOLINE + (userret - trampoline);
  ((void (*)(uint64,uint64))fn)(TRAPFRAME, satp);
}

测试结果,全部通过

(base) zhaokanglun@GANBADEI:~/s081/xv6-labs-2021$ make qemu
qemu-system-riscv64 -machine virt -bios none -kernel kernel/kernel -m 128M -smp 3 -nographic -drive file=fs.img,if=none,format=raw,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0

xv6 kernel is booting

hart 1 starting
hart 2 starting
init: starting sh
$ alarmtest
test0 start
.....................................alarm!
test0 passed
test1 start
......alarm!
.....alarm!
......alarm!
.....alarm!
....alarm!
.....alarm!
........alarm!
......alarm!
......alarm!
.....alarm!
.test1 passed
test2 start
..................................................................alarm!
test2 passed
$ 

你可能感兴趣的:(6.S081,LAB,risc-v,linux)