这个实验探索系统调用是如何通过trap实现的,会涉及到汇编代码和寄存器操作,建议先参考xv6手册以及xv6源码分析--trap机制。
一、RISC-V assembly
1 Which registers contain arguments to functions? For example, which register holds 13 in main's call to printf?
系统调用号保存在a7,参数保存在a0--a6,一般syscall会返回一个uint64放入到a0中,至于其他参数会在内核态直接写入用户指定的位置。printf中的参数13会被放在a2中。
2 Where is the call to function f in the assembly code for main? Where is the call to g? (Hint: the compiler may inline functions.)
汇编代码main函数中没有找到调用f的地方,应该是被内联取代函数调用了,直接设置为12。
3 At what address is the function printf located?
auipc rd, immediate意义是rd=immetiate<<12 pc。
代码语言:c复制void main(void) {
1c: 1141 addi sp,sp,-16
1e: e406 sd ra,8(sp)
20: e022 sd s0,0(sp)
22: 0800 addi s0,sp,16
printf("%d %dn", f(8) 1, 13);
24: 4635 li a2,13
26: 45b1 li a1,12
28: 00000517 auipc a0,0x0
2c: 7b050513 addi a0,a0,1968 # 7d8 <malloc 0xea>
//将0x0左移12位 pc值=pc值,
30: 00000097 auipc ra,0x0
34: 600080e7 jalr 1536(ra) # 630 <printf>
exit(0);
38: 4501 li a0,0
3a: 00000097 auipc ra,0x0
3e: 27e080e7 jalr 638(ra) # 2b8 <exit>
进入main函数时各个寄存器状态如下:
然后执行完auipc和addi后,寄存器状态:
最终jalr跳转到0x3ae位置,这就是printf syscall用户态地址。
5 In the following code, what is going to be printed after 'y='?
5221.
二、backtrace
1 问题分析
debugging时会有一个函数调用链,能够帮助我们发现在什么位置出现了什么error,实现这个功能,打印内核函数调用链。
- 通过stack frame pointer来打印调用链,gcc存储frame pointer在s0中。
- backtrace在kernel/printf.c中实现,并在sys_sleep中调用。
- xv6为每个进程分配了一页内核栈。
2 代码实现
代码语言:c复制void backtrace(void){
printf("backtrace: n");
uint64 fp=r_fp();
uint64 top=PGROUNDUP(fp);
uint64 bottom=PGROUNDDOWN(fp);
while (fp<top && fp>bottom){
uint64 last_fp=*(uint64*)(fp-16);
uint64 ra=*(uint64*)(fp-8);
printf("%pn",ra);
fp=last_fp;
}
}
三、alarm
1 问题分析
定时通知进程使用cpu的时间,这对一些需要限制cpu使用的进程有帮助。
- 增加系统调用sigalarm(interval, handler),每隔interval内核就调用一次handler。如果sigalarm(0, 0),内核就停止这个功能。
- interval、handler这两个应该存放到proc结构体中,并且还要追踪距离上次调用的时间间隔。
- cpu每隔一个tick,都会产生一个中断,在kernel/trap.c中由usertrap函数处理,会yield cpu,当前运行进程变为就绪态。
整个处理过程如图所示:
- 时钟中断(which_dev=2)时更新current_tick_num,一旦达到interval,就将用户态pc设置为alarm_handler,并设置in_handler=1。
- 如果in_handler=1就不能yield,而是应该立刻返回用户态,此时返回的位置是alarm_handler(periodic)。
- alarm_handler执行结束后通过sigreturn系统调用重新回到内核态,将原本的状态(alarmframe)赋值给trapframe,最重要的是用户态pc,设置为原本应该返回的位置,并将in_handler=0,下次中断就会yield该进程cpu。
2 代码实现
2.1 sigalarm
我们需要先在user 目录下添加系统调用声明代码,然后在kernel中添加系统调用实现,会从用户态传递两个参数。我们需要将这两个参数放入到proc结构体来维护,并且维护一个字段来追踪距离上次调用alarm间隔。
代码语言:c复制uint64
sys_sigalarm(void){
int ticks;
uint64 handleraddr;
if(argint(0, &ticks) < 0)
return -1;
if(argaddr(1, &handleraddr) < 0)
return -1;
struct proc *p=myproc();
if(ticks==0||handleraddr==0){
p->tick_interval=0;
p->alarm_handler=0;
}else{
p->alarm_handler=(void (*)())handleraddr;
}
return 0;
}
//proc.c
struct proc {
struct spinlock lock;
//...
//for Lab 4,定时alarm
int tick_interval;
void (*alarm_handler)(void);
int current_tick_num;
//...
};
每隔一个tick就会产生一个硬件时钟中断将进程yield出去,这个由usertrap处理,可以在这里判断是否需要执行进程的alarm_handler。alarm_handler是用户页表地址,内核页表没有对应页表项,所以需要切换到用户态去执行,结束后通过sigreturn进入到内核态。
代码语言:c复制void
usertrap(void)
{
int which_dev = 0;
//...
if(r_scause() == 8){
// system call
//...
} else if((which_dev = devintr()) != 0){
// ok
// printf("which_dev: %d, p->in_handler: %dn",which_dev,p->in_handler);
if(which_dev==2&&p->in_handler==0){
p->current_tick_num ;
//时钟中断时决定是否执行alarm_handler,并且不能够重入
if(p->current_tick_num>=p->tick_interval && p->tick_interval!=0){
acquire(&p->lock);
p->in_handler=1;
p->current_tick_num=0;
p->alarmframe=*p->trapframe;
//返回用户态,handler位置,执行完sigreturn进内核
//此次回到用户态是为了执行handler
p->trapframe->epc=p->alarm_handler;
release(&p->lock);
}
}
} else {
printf("usertrap(): unexpected scause %p pid=%dn", r_scause(), p->pid);
printf(" sepc=%p stval=%pn", r_sepc(), r_stval());
p->killed = 1;
}
if(p->killed)
exit(-1);
// give up the CPU if this is a timer interrupt.
if(which_dev == 2){
if(!p->in_handler){
//printf("pid: %d inhandlern",p->pid);
yield();
}
//如果in_handler=1,就直接返回用户态处理alarm_handler
}
usertrapret();
}
//sysproc.c
uint64
sys_sigalarm(void){
int ticks;
uint64 handleraddr;
if(argint(0, &ticks) < 0)
return -1;
if(argaddr(1, &handleraddr) < 0)
return -1;
struct proc *p=myproc();
p->tick_interval=ticks;
p->alarm_handler=handleraddr;
// printf("sigalarm ,interval: %d, handler: %ldn",p->tick_interval,p->alarm_handler);
return 0;
}
uint64
sys_sigreturn(void){
// printf("sigreturn ,n");
struct proc *p=myproc();
acquire(&p->lock);
*p->trapframe=p->alarmframe;
p->in_handler=0;
release(&p->lock);
return 0;
}