# Backtrace
大意是需要跟踪函数调用轨迹,根据栈的结构:
可以得出,用户态函数调用栈从高地址向低地址增长。栈帧指针 fp 向下 8 个字节是返回地址,向下 16 个字节指向调用者的栈。只要顺着调用者的栈不断向下查找就能输出完整的调用轨迹。
由于每个栈分配一个对齐的页,所以通过判断 fp 在不在当前页范围内就能知道有没有追踪到底。
void | |
backtrace(void){ | |
printf("backtrace:\n"); | |
uint64 fp=r_fp(); | |
uint64 up = PGROUNDUP(fp); | |
uint64 down = PGROUNDDOWN(fp); | |
while(fp<up && fp>down) { | |
printf("%p\n", *((uint64 *) (fp - 8)));// return address | |
fp = *((uint64 *) (fp - 16));//last fp | |
} | |
} |
# Alarm
简而言之,要实现一个定时输出 “alarm” 的程序,每隔指定的时钟中断周期就调用一次指定的函数(测试程序为输出 “alarm”)。
根据 hints,将 alarmtest 加入 makefile,相关的函数 sigalarm 和 sigreturn 加入到 usys.pl。
entry("sigalarm"); | |
entry("sigreturn"); |
在 syscall.c 中更新 syscall 调用表:
static uint64 (*syscalls[])(void) = { | |
//... | |
[SYS_sigalarm] sys_sigalarm, | |
[SYS_sigreturn] sys_sigreturn, | |
}; |
至此,已经可以通过命令行调用 alarmtest 函数。
接下来为了实现该功能,要在 proc 结构体中新增一些变量,打开 proc.h。
struct proc { | |
// ... | |
//for alarm | |
int ticks;// 多少个时钟周期触发 | |
int ticks_pass; // 已经经过的时钟周期数 | |
uint64 handler; // 要调用的处理函数 | |
struct alarm_context alarm_context; // 保存的寄存器 | |
int alarm_status; // 当前是否在 alarm 处理中 | |
}; |
为了在调用 alarm 之后,能恢复原来的程序,要保存的寄存器如下,保守起见,除了 t 系列寄存器其它都保存了。注意 alarm_context 在 proc 结构体中不要使用指针,否则需要分配空间,较为繁琐,若不分配会 panic: kernel trap。
struct alarm_context{ | |
/* 24 */ uint64 epc; // saved user program counter | |
/* 40 */ uint64 ra; | |
/* 48 */ uint64 sp; | |
/* 56 */ uint64 gp; | |
/* 64 */ uint64 tp; | |
/* 96 */ uint64 s0; | |
/* 104 */ uint64 s1; | |
/* 112 */ uint64 a0; | |
/* 120 */ uint64 a1; | |
/* 128 */ uint64 a2; | |
/* 136 */ uint64 a3; | |
/* 144 */ uint64 a4; | |
/* 152 */ uint64 a5; | |
/* 160 */ uint64 a6; | |
/* 168 */ uint64 a7; | |
/* 176 */ uint64 s2; | |
/* 184 */ uint64 s3; | |
/* 192 */ uint64 s4; | |
/* 200 */ uint64 s5; | |
/* 208 */ uint64 s6; | |
/* 216 */ uint64 s7; | |
/* 224 */ uint64 s8; | |
/* 232 */ uint64 s9; | |
/* 240 */ uint64 s10; | |
/* 248 */ uint64 s11; | |
}; |
然后更改 usertrap 函数,在时钟中断中加入处理逻辑。
void | |
usertrap(void) | |
{ | |
// ... | |
// give up the CPU if this is a timer interrupt. | |
if(which_dev == 2){ | |
if(p->ticks>0){ | |
p->ticks_pass++; | |
if(p->ticks_pass>=p->ticks&&!(p->alarm_status)){ | |
p->alarm_status=1; // 当前正在处理 alarm, 不响应其它 alarm | |
p->ticks_pass=0; | |
alarm_store();// 保存寄存器到当前 proc | |
p->trapframe->epc=p->handler; // 指定的处理函数 | |
} | |
} | |
yield(); | |
} | |
usertrapret(); | |
} |
alarm_store 就是复制寄存器,无脑。
void | |
alarm_store(void) | |
{ | |
struct proc *p = myproc(); | |
p->alarm_context.epc=p->trapframe->epc; | |
p->alarm_context.a0=p->trapframe->a0; | |
p->alarm_context.a1=p->trapframe->a1; | |
p->alarm_context.a2=p->trapframe->a2; | |
p->alarm_context.a3=p->trapframe->a3; | |
p->alarm_context.a4=p->trapframe->a4; | |
p->alarm_context.a5=p->trapframe->a5; | |
p->alarm_context.a6=p->trapframe->a6; | |
p->alarm_context.a7=p->trapframe->a7; | |
p->alarm_context.sp=p->trapframe->sp; | |
p->alarm_context.s0=p->trapframe->s0; | |
p->alarm_context.s1=p->trapframe->s1; | |
p->alarm_context.s2=p->trapframe->s2; | |
p->alarm_context.s3=p->trapframe->s3; | |
p->alarm_context.s4=p->trapframe->s4; | |
p->alarm_context.s5=p->trapframe->s5; | |
p->alarm_context.s6=p->trapframe->s6; | |
p->alarm_context.s7=p->trapframe->s7; | |
p->alarm_context.s8=p->trapframe->s8; | |
p->alarm_context.s9=p->trapframe->s9; | |
p->alarm_context.s10=p->trapframe->s10; | |
p->alarm_context.s11=p->trapframe->s11; | |
p->alarm_context.gp=p->trapframe->gp; | |
p->alarm_context.ra=p->trapframe->ra; | |
p->alarm_context.tp=p->trapframe->tp; | |
} |
初始化 proc 也要增加一些字段:
static struct proc* | |
allocproc(void) | |
{ | |
//... | |
//init alarm | |
memset(&p->alarm_context,0,sizeof(p->alarm_context)); | |
p->ticks=0; | |
p->alarm_status=0; | |
p->ticks_pass=0; | |
p->handler=0; | |
return p; | |
} |
最后是新增加的两个函数,写在 sysproc.c 中:
uint64 | |
sys_sigalarm(void){// 主要为了获取参数 | |
int ticks; | |
uint64 handler; | |
argint(0,&ticks); | |
argaddr(1,&handler); | |
struct proc*p=myproc(); | |
p->ticks=ticks; | |
p->handler=handler; | |
p->ticks_pass=0; | |
return 0; | |
} | |
uint64 | |
sys_sigreturn(void){// 主要为了恢复现场 | |
struct proc*p=myproc(); | |
p->trapframe->epc=p->alarm_context.epc; | |
p->trapframe->a0=p->alarm_context.a0; | |
p->trapframe->a1=p->alarm_context.a1; | |
p->trapframe->a2=p->alarm_context.a2; | |
p->trapframe->a3=p->alarm_context.a3; | |
p->trapframe->a4=p->alarm_context.a4; | |
p->trapframe->a5=p->alarm_context.a5; | |
p->trapframe->a6=p->alarm_context.a6; | |
p->trapframe->a7=p->alarm_context.a7; | |
p->trapframe->sp=p->alarm_context.sp; | |
p->trapframe->s0=p->alarm_context.s0; | |
p->trapframe->s1=p->alarm_context.s1; | |
p->trapframe->s2=p->alarm_context.s2; | |
p->trapframe->s3=p->alarm_context.s3; | |
p->trapframe->s4=p->alarm_context.s4; | |
p->trapframe->s5=p->alarm_context.s5; | |
p->trapframe->s6=p->alarm_context.s6; | |
p->trapframe->s7=p->alarm_context.s7; | |
p->trapframe->s8=p->alarm_context.s8; | |
p->trapframe->s9=p->alarm_context.s9; | |
p->trapframe->s10=p->alarm_context.s10; | |
p->trapframe->s11=p->alarm_context.s11; | |
p->trapframe->gp=p->alarm_context.gp; | |
p->trapframe->ra=p->alarm_context.ra; | |
p->trapframe->tp=p->alarm_context.tp; | |
p->alarm_status=0; | |
return p->alarm_context.a0;// 返回保存的 a0 寄存器,因为之前的返回值放在 a0 中,而其它中断返回值会覆盖 a0 | |
} |
为什么需要设置 alarm 状态,判断是否在 alarm 中?因为处理中断要花费的时钟周期可能比指定触发间隔更长,为避免无限嵌套中断,需要拒绝执行正在 alarm 状态中收到的 alarm 中断请求。