0%

Lab Traps

Lab Traps

前置知识

一、stack

  1. stack储存方式和heap相反,它是向下增长的,有两个重要的寄存器,fp和sp。fp指向当前frame的顶(上),sp指向的是当前frame的底(下)。
  2. stack中函数的return address和上一个fp的地址储存在当前fp的固定偏移量上,通过lab hints我们可以知道一个是-8,一个是-16。

Task RISC-V assembly

根据实验提示,我们make fs.img出user/call.asm来分析。

1.

1
24:	4635                	li	a2,13

可以看出a2储存了13。

2.

1
26:	45b1                	li	a1,12

可以看出编译器使用了inline优化,直接算出了12并储存在a1中。

3.

1
2
30:	00000097          	auipc	ra,0x0
34: 610080e7 jalr 1552(ra) # 640 <printf>

可以看出printf在640位置上。注意auipc这个指令,它把0这个立即数左移12位并加上pc(+4)的值后赋给了ra,然后加上了1552找到了printf

  1. jalr这个指令会把当前pc加4(指向下一条pc指令)并赋给ra

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

    输出的是HE110 World。前一个%x是十六进制转换,后一个因为riscv是”little-endian”,他会从后往前转换字符。

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

    因为printf的format储存在a0,3存储在a1,所以当它试图print第二个数时,就会把a2中的无关值输出出来。

Task Backtrace

这个task要求我们实现一个回溯的syscall,它通过遍历整个stack来输出所有stack frame中的函数地址。

  1. 根据提示添加r_fp(),并在sys_sleep中添加backtrace()。
  2. 因为fp的存在和上一个fp地址的储存,我们可以通过类似遍历链表的方式将stack从下到上遍历一遍,每遍历到一个frame我们就print出它储存的return address。注意添加def.h
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // printf.c
    void backtrace(void)
    {
    printf("backtrace:\n");
    uint64 cur_fp = r_fp(); // 当前fp
    uint64 top_fp = PGROUNDUP(cur_fp); // PGROUNDUP可以找到当前stack的顶
    while(top_fp > cur_fp)
    {
    uint64 va = *(pte_t *)(cur_fp - 0x08);
    printf("%p\n", va);
    cur_fp = *(pte_t *)(cur_fp - 0x10); // 跳转到前一个fp
    }
    }

Task Alarm

这个task要求我们实现一个alarm功能,每当clock中断执行一定的次数后,就会调用这个alarm函数(注意这个函数是xv6自带的),输出一个alarm!。

  1. 首先,我们需要在proc结构体里保存三个需要的参数,分别是clock需要执行的次数,alarm函数的地址和clock已经执行的次数,并把它们在allocproc初始化。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // proc.h
    ...
    char name[16]; // Process name (debugging)
    int alarm_pe;
    void (*alarm_handler)();
    int ticks;
    ...
    ...
    // proc.c/ allocproc()
    p->alarm_handler = 0;
    p->alarm_pe = 0;
    p->ticks = 0;
    ...
  2. 当一个用户函数要初始化alarm功能时,他需要呼叫sys_sigalarm。我们需要实现这个syscall。关于sys_sigreturn,你可以先写上它并只让它返回0。(还记得那些步骤吗,此处省略了)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    uint64 
    sys_sigalarm(void)
    {
    struct proc *prc = myproc();
    int period;
    if (argint(0, &period) < 0) // 读入周期
    return -1;
    uint64 p;
    if (argaddr(1, &p) < 0) // 读入alarm地址
    return -1;

    prc->alarm_pe = period;
    prc->alarm_handler = (void(*)())p;
    prc->ticks = 0;
    return 0;
    }

    uint64
    sys_sigreturn(void){
    return 0;
    }
  3. 我们需要修改usertrap,每次发生clock interrupt的时候给ticks加一,当ticks等于alarm_pe的时候就将alarm地址写入异常中断寄存器epc中。然后系统就会中断并自动调用alarm。此时第一个小任务完成。
    1
    2
    3
    4
    5
    6
    if(which_dev == 2) {
    p->ticks ++;
    if (p->alarm_pe != 0 && p->ticks == p->alarm_pe && p->isalarm == 0) {
    p->trapframe->epc = (uint64)p->alarm_handler;//set the address of syscall alarm
    p->ticks = 0;//it can also be set in sys_sigreturn
    }
  4. 因为alarm调用被设为一个中断,所以必须将调用alarm前所有的user寄存器保存起来,防止中断后对用户寄存器的复写。同时,因为xv6并行,在处理完一个alarm之前不能进行另一个alarm的处理,因此我们需要一个标志来记录当前是否有alarm在处理。因此需要在proc里再添加两个参数。它们需要被初始化,trapframe用完要释放。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    // proc.h
    int isalarm;
    struct trapframe *atrapframe;

    // proc.c/ allocproc()
    p->isalarm = 0;

    // proc.c/ freeproc()
    static void
    freeproc(struct proc *p)
    {
    if(p->trapframe)
    kfree((void*)p->trapframe);
    if(p->atrapframe)
    kfree((void*)p->atrapframe); // 释放
    p->trapframe = 0;
    p->atrapframe = 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;
    p->state = UNUSED;
    p->isalarm = 0; // 恢复
    }
  5. 修改usertrap来保存寄存器并设置isalarm。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    if(which_dev == 2) {
    p->ticks ++;
    if (p->alarm_pe != 0 && p->ticks == p->alarm_pe && p->isalarm == 0) {
    p->isalarm = 1;//set alarm flag
    *p->atrapframe = *p->trapframe;//备份寄存器
    p->trapframe->epc = (uint64)p->alarm_handler;//set the address of syscall alarm
    p->ticks = 0;//it can also be set in sys_sigreturn
    }
    yield();
    }
  6. 最后,在sigreturn中,我们需要恢复所有的寄存器,恢复isalarm。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    uint64 
    sys_sigreturn(void){
    struct proc* p = myproc();
    if (p->isalarm){
    p->isalarm = 0;
    *p->trapframe = *p->atrapframe;//恢复寄存器
    }
    return 0;
    }
    至此,lab traps结束。