lab4

trap步骤(跳转到trampoline):

交换 a0sscratch

  • 使用 csrrw a0, sscratch, a0 指令交换 a0sscratch 的值。这样,a0 现在指向 TRAPFRAME,这是用户空间的 trapframe 地址,而 sscratch 保存了原来的 a0 值(即用户态传递的参数)。

保存用户态寄存器

  • 将所有用户态寄存器的值保存到 a0 指向的 trapframe 中,以便在处理完陷阱后可以恢复这些寄存器的值。

保存用户态的 a0

  • sscratch(即原来的 a0)保存到 trapframe 中,以便在返回用户态时可以恢复 a0 的值。

恢复内核态上下文

  • 从 trapframe 中恢复内核栈指针(sp)、当前 hart 的 id(tp)、usertrap 函数的地址(t0),以及内核页表(satp)。

跳转到 usertrap

  • 使用 jr t0 跳转到 usertrap() 函数,这个函数处理实际的陷阱(如系统调用、中断或异常),并不会返回。

Backtrace

首先,按照指导书的提示将读取fp(s0)寄存器的指令写入riscv.h,并添加backtrace()sys_sleep

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static inline uint64
r_fp()
{
uint64 x;
asm volatile("mv %0, s0" : "=r" (x) );
return x;
}

uint64
sys_sleep(void)
{
...
backtrace();
return 0;
}

printf.c定义我们的backtrace()函数,由于xv6的栈只分配了一页的空间,我们可以直接获取栈底(页开始)的地址,当ret_addr大于栈底时(栈是反向增长的),即已完成遍历。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//defs.h
void backtrace(void);

//printf.c
void
backtrace()
{
printf("backtrace:\n");
uint64 fp = r_fp();
uint64 base = PGROUNDUP(fp);
while(fp < base)
{
uint64 Ret_addr = *(uint64*)(fp -8); //返回地址
fp = *(uint64*)(fp -16); //上一个fp指针
printf("%p\n", Ret_addr);
}
}

Alarm

test0

按照添加系统调用的流程,添加系统调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//usys.pl
entry("sigalarm");
entry("sigreturn");

//usys.h
int sigalarm(int ticks, void (*handler)());
int sigreturn(void);

//syscall.h
#define SYS_sigalarm 22
#define SYS_sigreturn 23



//syscall.c
extern uint64 sys_sigalarm(void);
extern uint64 sys_sigreturn(void);
static uint64 (*syscalls[])(void) = {
...
[SYS_sigalarm] sys_sigalarm,
[SYS_sigreturn] sys_sigreturn,
};

为了实现定时alram,我们需要在proc添加字段来保存handler函数、tick次数和触发handler的ticks次数。同时在创建进程时对其进行初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct proc {
...

uint64 handler_addr;
int ticks;
int tickcount;
};

void
userinit(void)
{
...
p->tickcount = 0;
p->ticks = 0;
p->handler_addr = 0;
release(&p->lock);
}

我们添加sys_sigalarmsys_sigreturn函数,由于test0只是测试handler,我们可以将sys_sigreturn直接返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

uint64
sys_sigalarm(void)
{
int ticks;
uint64 handler;
if(argint(0, &ticks) < 0||argaddr(1, &handler) < 0)
{
return -1;
}
struct proc *p = myproc();
p->tickcount = 0;
p->ticks = ticks;
p->handler_addr = handler;
return 0;
}

uint64
sys_sigreturn(void)
{
return 0;
}

修改时钟中断,每次中断都将tick加1,当满足tick==ticks时调用handler,并将tick归0。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if(which_dev == 2)
{
if(p->ticks){
p->tickcount ++;
if(p->tickcount == p->ticks)
{
p->tickcount = 0;
p->ticks = 0;
p->trapframe->epc = p->handler_addr;
}

}
yield();
}

test1

进入test1,要求我们要恢复寄存器状态,即保存trapframe,并在调用sys_sigreturn时恢复寄存器。因此我们要添加old_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
struct proc {
...
uint64 handler_addr;
int ticks;
int tickcount;
struct trapframe *old_trapframe;
};



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

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

}

接着修改sys_sigreturn,恢复寄存器

1
2
3
4
5
6
7
8
9
uint64
sys_sigreturn(void)
{
struct proc *p = myproc();
*(p->trapframe) = *(p->old_trapframe); //注意这里是深拷贝,将所有寄存器都进行复制


return 0;
}

在进入handler函数前保存寄存器。为什么不在调用sys_sigalarm()时就保存呢?因为我们设置中断,进程还是照常执行,只不过每隔tick时钟执行一次handler。寄存器的状态显然不一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 if(which_dev == 2)
{
if(p->ticks){
p->tickcount ++;
if(p->tickcount >= p->ticks)
{
p->tickcount = 0;
*p->old_trapframe = *p->trapframe;
p->trapframe->epc = p->handler_addr;
}

}
yield();
}
usertrapret();
}

test2

test2测试的是当handler函数执行所需的时间大于达到tick==ticks的时间,此时handler被重入,而我们的中断处理程序是不允许重入的(若重入则不仅保存的寄存器数据被覆盖,handler还会陷入死循环),因此我们需要添加一个字段防止这种事情发生。

添加is_alarm字段,当handler执行时,另一个handler将不会被调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void
userinit(void)
{

p->tickcount = 0;
p->ticks = 0;
p->handler_addr = 0;
p->is_alarm = 0;

release(&p->lock);
}



修改sys_sigalarmsys_sigreturn函数

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
uint64
sys_sigalarm(void)
{
int ticks;
uint64 handler;
if(argint(0, &ticks) < 0||argaddr(1, &handler) < 0)
{
return -1;
}
struct proc *p = myproc();
p->tickcount = 0;
p->ticks = ticks;
p->handler_addr = handler;
p->is_alarm = 0;
return 0;
}

uint64
sys_sigreturn(void)
{
struct proc *p = myproc();
*(p->trapframe) = *(p->old_trapframe);
p->is_alarm = 0;

return 0;
}

修改时钟中断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if(which_dev == 2)
{
if(p->ticks){
p->tickcount ++;
if(p->tickcount >= p->ticks &&p->is_alarm == 0)
{
p->tickcount = 0;
*p->old_trapframe = *p->trapframe;
p->is_alarm = 1;
p->trapframe->epc = p->handler_addr;

}

}
yield();
}