一 实验原理
当用户进程发生系统调用、中断或者是异常时,操作系统需要保存用户进程的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 实验结果