6.S081 Lab: traps

6.S081 Lab4: traps

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

哪个寄存器存储函数的参数?例如,在main对printf的调用中哪个寄存器存了13?

a0-a7;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.)

这里对f()和g()的调用被优化了,在汇编代码中可以看到f(8)+1被直接优化成了12

3、At what address is the function printf located?

0x630

4、What value is in the register ra just after the jalr to printf in main?

0x38

5、Run the following code.

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

What is the output? The output depends on that fact that the RISC-V is little-endian. If the RISC-V were instead big-endian what would you set i to in order to yield the same output? Would you need to change 57616 to a different value?

He110 World; 0x726c6400; No, 57616 is 110 in hex regardless of endianness

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

A random value,my output is 0; '3' is passed to a1, and what is printed after 'y=' depends on what in the a2 before the call

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>

在RISC-V汇编中:

Backtrace

这个lab实际就是要求写一个backtrace函数,能够打印出frame pointer所指的地址处return address

这个slide描述了堆栈的结构

根据提示,我们有了r_fp()可以取出s0中的frame pointer,那么剩下的工作就是遍历栈,打印出所有的return address,请注意,返回地址与堆栈帧指针的固定偏移量(-8),保存的帧指针与帧指针的固定偏移量(-16)

void backtrace(){
  uint64 frame_point = r_fp();
  while(frame_point != PGROUNDUP(frame_point)){
    printf("%p\n", *(uint64*)(frame_point-8));
    frame_point = *(uint64*)(frame_point-16);
  }
}

在sys_sleep开头插入backtrace()的调用:

uint64
sys_sleep(void)
{
  int n;
  uint ticks0;

  backtrace();
  if(argint(0, &n) < 0)
    return -1;
  acquire(&tickslock);
  ticks0 = ticks;
  while(ticks - ticks0 < n){
    if(myproc()->killed){
      release(&tickslock);
      return -1;
    }
    sleep(&ticks, &tickslock);
  }
  release(&tickslock);
  return 0;
}

在defs.h中添加backtrace()的声明:

// printf.c
void            printf(char*, ...);
void            panic(char*) __attribute__((noreturn));
void            printfinit(void);
// new
void            backtrace(void);

Alarm

在本练习中,需要向xv6添加一个特性,该特性在进程使用CPU时定期向其发出警报。这对于希望限制占用多少CPU时间的受计算限制的进程,或者对于希望进行计算但又希望执行某些周期性操作的进程,可能很有用。更一般地说,将要实现的是用户级中断/故障处理程序的原始形式;例如,可以使用类似的方法来处理应用程序中的页面错误。

第一步是先添加一个sigalarm(interval, handler)函数调用,如果有一个应用调用了sigalarm(n, fn),每隔 n 个 "ticks" 的CPU时间,内核就会调用一次fn(),并且 fn返回后,应用应该回到调用前的状态

以 test0 为例:

// tests whether the kernel calls
// the alarm handler even a single time.
void
test0()
{
  int i;
  printf("test0 start\n");
  count = 0;
  sigalarm(2, periodic);
  for(i = 0; i < 1000*500000; i++){
    if((i % 1000000) == 0)
      write(2, ".", 1);
    if(count > 0)
      break;
  }
  sigalarm(0, 0);
  if(count > 0){
    printf("test0 passed\n");
  } else {
    printf("\ntest0 failed: the kernel never called the alarm handler\n");
  }
}

sigalarm(2, periodic)先调用sigalarm(), 将时间间隔设为 2,每隔2 个 ticks调用程序 periodic,然后调用 sigalarm(0, 0), 内核 停止 生成 periodic alarm calls.

具体步骤:

先修改 Makefile 让 alarmtest.c 能被编译为一个xv6用户程序:

user/user.h 中声明:

int sigalarm(int ticks, void (*handler)());
int sigreturn(void);

更新 user/usys.pl , kernel/syscall.h, and kernel/syscall.c 让 alarmtest 能够进行 sigalarm 和 sigreturn 系统调用.

sys_sigalarm 和 sys_sigreturn系统调用:

uint64 sys_sigalarm(void){
  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();
}

struct proc中增加几个字段:

int alarm_interval; // 时间间隔
void(*alarm_handler)(); // 处理程序
int interval_num; // 已经过了多少个 ticks
int pre_end; // 上一个 sigalarm 是否已经结束
struct trapframe *alarm_trapframe; // 用来 save and restore registers

初始化 proc fields in allocproc() in proc.c

static struct proc*
allocproc(void)
{
  // ...

  // Allocate a trapframe page.
  if((p->trapframe = (struct trapframe *)kalloc()) == 0){
    release(&p->lock);
    return 0;
  }
  
  // new
  if((p->alarm_trapframe = (struct trapframe *)kalloc()) == 0){
    release(&p->lock);
    return 0;
  }

  // An empty user page table.
  p->pagetable = proc_pagetable(p);
  if(p->pagetable == 0){
    freeproc(p);
    release(&p->lock);
    return 0;
  }

  // Set up new context to start executing at forkret,
  // which returns to user space.
  memset(&p->context, 0, sizeof(p->context));
  p->context.ra = (uint64)forkret;
  p->context.sp = p->kstack + PGSIZE;
  
  // new
  p->interval_num = 0;
  p->alarm_interval = 0;
  p->alarm_handler = 0;
  p->pre_end = 1;
  return p;
}

static void
freeproc(struct proc *p)
{
  if(p->trapframe)
    kfree((void*)p->trapframe);
  p->trapframe = 0;
    
  if(p->alarm_trapframe)
    kfree((void*)p->alarm_trapframe);
  p->alarm_trapframe = 0;
    
  if(p->pagetable)
    proc_freepagetable(p->pagetable, p->sz);
  p->pagetable = 0;
  p->sz = 0;
  p->pid = 0;
  p->parent = 0;
  p->name[0] = 0;
  p->chan = 0;
  p->killed = 0;
  p->xstate = 0;
  // new
  p->alarm_handler = 0;
  p->alarm_interval = 0;
  p->interval_num = 0;
  p->pre_end = 1;
  p->state = UNUSED;
}

在trap.c 中增加sigalarm() 和 sigreturn() 函数:

int sigalarm(int ticks, void (*handler)()){
  struct proc *p = myproc();
  p->alarm_interval = ticks;
  p->interval_num = 0;
  p->alarm_handler = handler;
  return 0;
}

int sigreturn(void){
  struct proc *p = myproc();
  p->pre_end = 1; // 上一个sigalarm已经结束
  *p->trapframe = *p->alarm_trapframe; // 恢复寄存器
  return 0;
}

修改usertrap() 函数:

void
usertrap(void)
{
  // ...

  // give up the CPU if this is a timer interrupt.
  if(which_dev == 2){
    if(p->alarm_interval != 0){
      if(p->pre_end != 0){ // 没有别的sigalarm 在运行
        p->interval_num += 1;
        if(p->interval_num >= p->alarm_interval){ // 到了规定的计数次数
          p->interval_num = 0; // 计数清零
          *p->alarm_trapframe = *p->trapframe; // 保存寄存器
          p->pre_end = 0;
          // CPU由内核态退出,返回到用户模式时,会将sepc寄存器的值加载到pc寄存器,进而跳转到sepc指向的代码处执行
          p->trapframe->epc = (uint64)p->alarm_handler; // epc 用来保存user program counter
        }
      }
    }

    yield();
  }
  usertrapret();
}
posted @ 2022-02-25 13:07  east-dong  阅读(152)  评论(0)    收藏  举报