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就是上一个函数的栈帧,就是上一个函数的最初的位置。理解了这些,就可以进行实验了。

image

image

  1. 要得到上一个函数的地址,需要访问当前fp,代码如下
static inline uint64
r_fp()
{
  uint64 x;
  asm volatile("mv %0, s0" : "=r" (x) );
  return x;
}
  1. 得到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和处理函数保存,然后过了intervalsticks。就可以调用handler了。

kernel/sysproc.csys_sigalarmsys_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,增加进入handlertrapframe的保存。以及改变状态来避免再次进入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));导致测试无法通过。

细节真的很重要!

posted @ 2025-07-12 15:54  BuerH  阅读(15)  评论(0)    收藏  举报