Lab traps

一 实验原理

        当用户进程发生系统调用、中断或者是异常时,操作系统需要保存用户进程的CPU现场,并且从用户态切换到内核态,之后内核执行trap handler程序。内核处理完trap后,再返回到用户态。在xv6中,当用户进程因为系统调用ecall指令进入到内核态,内核在执行uservec和usertrap函数,之后执行系统调用相关的函数,最后通过usertrapret和userret返回到用户态。

二 实验部分

2.1 backtrace

       

 

        backtrace实现的是当出现错误时,打印函数调用栈中所有的已经调用的函数的返回地址。RISC-V中,一个指针是8B,return address是fp-8,prev frame是fp-16。

1 在kernel/defs.h头文件中加上backtrace声明

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

 2 在kernel/riscv.h中添加函数以获得当前调用函数stack frame的fp指针

static inline uint64
r_fp()
{
  uint64 x;
  asm volatile("mv %0,s0":"=r" (x));
  return x;
}

3 backtrace函数,遍历stack。stack是page-aligned,循环终止的条件是p的地址不超出stack的page范围

void
backtrace(void)
{
  uint64 fp=r_fp();
  for(uint64 p=fp;p>=PGROUNDDOWN(fp)&&p<=PGROUNDUP(fp);p=*(uint64*)(p-16)){
    printf("%p\n",*(uint64*)(p-8));
  }
}

4 在sys_sleep和panic中调用backtrace函数

2.2 Alarm

       本题是实现两个系统调用,一个是sigalarm(n,fn),一个是sigreturn(),目的是让用户进程每n个时钟周期调用一次fn函数。

1 Makefile中添加alarmtest.c,以保证后续的编译可以通过

UPROGS=\
	$U/_cat\
	$U/_echo\
	$U/_forktest\
	$U/_grep\
	$U/_init\
	$U/_kill\
	$U/_ln\
	$U/_ls\
	$U/_mkdir\
	$U/_rm\
	$U/_sh\
	$U/_stressfs\
	$U/_usertests\
	$U/_grind\
	$U/_wc\
	$U/_zombie\
	$U/_alarmtest\

2 user/user.h中添加sigalarm和sigreturn的声明

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

3 更新user/usys.pl,kernel/syscall.h,kernel/syscall.c

//user.usys.pl
...
entry("sigalarm");
entry("sigreturn");

//kernel/syscall.c
extern uint64 sys_sigalarm(void);
extern uint64 sys_sigreturn(void);

static uint64 (*syscalls[])(void) = {
[SYS_fork]    sys_fork,
[SYS_exit]    sys_exit,
[SYS_wait]    sys_wait,
[SYS_pipe]    sys_pipe,
[SYS_read]    sys_read,
[SYS_kill]    sys_kill,
[SYS_exec]    sys_exec,
[SYS_fstat]   sys_fstat,
[SYS_chdir]   sys_chdir,
[SYS_dup]     sys_dup,
[SYS_getpid]  sys_getpid,
[SYS_sbrk]    sys_sbrk,
[SYS_sleep]   sys_sleep,
[SYS_uptime]  sys_uptime,
[SYS_open]    sys_open,
[SYS_write]   sys_write,
[SYS_mknod]   sys_mknod,
[SYS_unlink]  sys_unlink,
[SYS_link]    sys_link,
[SYS_mkdir]   sys_mkdir,
[SYS_close]   sys_close,
[SYS_sigalarm] sys_sigalarm,
[SYS_sigreturn] sys_sigreturn,
};

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

4 sigalarm系统调用传递参数,将n和fn保存到struct proc中。

//kernel/proc.h中struct proc添加的相关变量
  int interval;  //保存sigalarm传入的参数n 
  uint64 handler;  //保存sigalarm传入的参数fn
  int ticks;       //记录当前经历过的时钟次数
  int flag;        //用于标记此时是否在handler函数中,0表示不在,1表示在
  struct trapframe *copy; //用于拷贝被打断处的用户进程的CPU现场

//kernel/sysproc.c
uint64
sys_sigalarm(void)
{
  int ticks;
  uint64 fn;
  if(argint(0,&ticks)<0)
    return -1;
  if(argaddr(1,&fn)<0)
    return -1;
  myproc()->interval=ticks;
  myproc()->handler=fn;
  return 0;
}

//kernel/proc.c中allocproc函数,对proc结构体变量中的ticks和flag进行初始化,初始化为0
  // initialize the ticks and flags
  p->ticks=0;
  p->flag=0;

5 用户进程每次时钟中断时,都要对p->ticks++,表明此时经历的时钟中断次数,当该值等于p->interval时,需要回到用户态执行handler函数。而此时用户进程的CPU现场可能会被覆盖,因此需要复制此时的p->trapframe到p->copy。另外只有当用户进程不是在执行handler函数时才需要对p->ticks进行计数,否则会一直调用handler函数而出现死循环,flag标记为0时表示此时不在handler函数中。

//kernel/trap.c中的usertrap函数
  // give up the CPU if this is a timer interrupt.
  if(which_dev == 2){
    yield();
    if(!p->flag){
      p->ticks++;
      if(p->ticks==p->interval){
        p->copy=(struct trapframe*)kalloc();
	    memmove(p->copy,p->trapframe,sizeof(struct trapframe));
	    p->trapframe->epc=p->handler;
	    p->flag=1;
	    p->ticks=0;
      }
    }
  }

6 执行完handler时,通过一个系统调用sigreturn,回到进程原来执行的位置。要恢复原来的trapframe,并把flag重新置为0。

uint64
sys_sigreturn(void)
{
  struct proc *p=myproc();

  memmove(p->trapframe,p->copy,sizeof(struct trapframe));
  kfree(p->copy);
  p->flag=0;
  return 0;
}

2.3 实验结果

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值