MIT6.s081_Lab4 traps: Trap
MIT6.s081 Lab4:Traps
之前的实验实现过Syscall,这个实验是为了更深刻的理解如何进入的系统调用。简单来说,Backtrace打印函数的调用信息,Alarm根据时钟信息来调用传入的函数。
1. Backtrace
Lab中是在sys_sleep中调用的,要打印所有的调用信息,必须访问进程的栈区,下面配两张图,方便理解。xv6中调用一个函数,会有callee需要保存的数据,首先会将stack pointer下移16个字节,保存return address也就是ra,然后保存frame pointer(fp/s0),这两个是不会变得地址,这样也就给函数的回溯带来的方便,return address就是调用这个函数的地址,fp就是上一个函数的栈帧,就是上一个函数的最初的位置。理解了这些,就可以进行实验了。


- 要得到上一个函数的地址,需要访问当前
fp,代码如下
static inline uint64
r_fp()
{
uint64 x;
asm volatile("mv %0, s0" : "=r" (x) );
return x;
}
-
得到
fp后这个地址-8就能得到调用这个函数的地址了,然后在更新这个fp循环打印就可void backtrace(void) { uint64 prev_fp = r_fp(); printf("backtrace:\n"); while(prev_fp < PGROUNDUP(r_fp())) { uint64 ra = *(uint64 *)(prev_fp - 8); printf("%p\n", ra); prev_fp = *(uint64 *)(prev_fp - 16); } }
2. Alarm
Alarm根据系统的时钟,定时执行函数调用。
这里要调用这个sys_alarm,先添加定义,用户空间添加函数,增加入口号,增加系统调用号。
user/user.h中添加两个函数
int sigalarm(int ticks, void (*handler)());
int sigreturn(void);
kernel/syscall.h中定义两个宏
#define SYS_sigalarm 22
#define SYS_sigreturn 23
kernel/syscall.c中添加相关定义
extern uint64 sys_sigalarm(void);
extern uint64 sys_sigreturn(void);
[SYS_sigalarm] sys_sigalarm,
[SYS_sigreturn] sys_sigreturn,
test0中,要求比较简单,先只过test0的测试。有一个hints,每滴答,硬件时钟强制一个中断,该中断在 usertrap() 中由 kernel/trap.c 处理。which_dev == 2的时候就是时钟中断了,想要定时触发一个函数,就需要增加一些额外的变量了。
首先就是间隔,多长时间调用一次函数,然后是调用哪个函数,最后就是当前的时间已经过去了多少,当间隔为0的时候可以当作没有这个挂起的时钟处理函数。
kernel/proc.h添加下面变量
int intervals; //间隔ticks数
void (*handler)(void); //调用的函数
int ticks; //当前过去的ticks
初始化这些添加的变量,kernel/proc.c添加初始化
p->intervals = 0;
p->handler = 0x0;
p->ticks = 0;
调用alarm的时候,需要将intervals和处理函数保存,然后过了intervals的ticks。就可以调用handler了。
kernel/sysproc.c中sys_sigalarm和sys_sigreturn的实现。
uint64
sys_sigalarm(void)
{
argint(0, &(myproc()->intervals));
argaddr(1, (uint64 *)(&(myproc()->handler)));
return 0;
}
uint64
sys_sigreturn(void)
{
return 0;
}
接下来就是定时的处理了。每次时钟中断会进入traps处理,这里只需要对时钟中断做处理即可。修改kernel/trap.c。这里只可通过test0
if(which_dev == 2){
if(p->intervals != 0) {
if(p->isHandle == 0) {
p->ticks++;
if(p->ticks >= p->intervals) {
p->ticks = 0;
p->trapframe->epc = (uint64)(p->handler);
}
}
yield();
}
test1/test2():resume interrupted code,恢复中断的代码。
之前的调用,修改用户空间的PC直接执行handler函数,这样会破坏之前进程运行的环境,traps需要恢复调用前的环境,因此我们需要在调用handler之前,保存之前的环境,并且没有执行完handler之前,不可以再次因为ticks进入handler,这里就需要加入一个类似于锁的东西了。
多加两个变量如下,一个是保存frame,另一个就是实现锁这样的东西了。
struct trapframe alarm_trapframe;
int isHandle;
初始化进程也增加对isHandle的初始化
p->isHandle = 0;
修改trap.c,增加进入handler对trapframe的保存。以及改变状态来避免再次进入handler。
p->isHandle = 1;
memmove(&p->alarm_trapframe, p->trapframe, sizeof(struct trapframe));
修改sys_sigreturn,以便返回的时候能恢复之前的现场信息。
uint64
sys_sigreturn(void)
{
memmove(myproc()->trapframe, &myproc()->alarm_trapframe, sizeof(struct trapframe));
myproc()->isHandle = 0;
return 0;
}
上面全部修改完,就可以通过了。
遇到的一些问题如下:
trapframe刚开始是用指针,申请的页空间,然后需要申请和释放,第一次测试的时候有内存泄漏。然后增加了释放。然后进行了改善。还有一些关于指针、地址的问题,memmove忘记修改memmove(myproc()->trapframe, &myproc()->alarm_trapframe, sizeof(struct trapframe));导致测试无法通过。细节真的很重要!

浙公网安备 33010602011771号