1. 写在前面
感觉这个lab给我最大的收获就是:不加参数的cat,用ctrl+D退出(笑)。之前用cat的时候忘了写参数然后就陷入cat的循环里去了,不知道怎么退出,就只好强行关掉shell。另外,addr2line这个指令挺有用的,用于把地址翻译到相应函数。记录一下。
2. lab任务流程
有三个任务:回答汇编问题,写一个backtrace(虽然我感觉毫无帮助),外加一个alarm调用实现。
3. backtrace实现
这个backtrace也算有点收获,认识到了s0寄存器在函数调用约定中的作用,s0作为帧指针,指向该函数栈帧的第一个字节(虽然我觉得如果优化级别开高一点gcc就不会使用帧指针了?)。
void
backtrace(){
uint64 s0 = r_fp();
uint64 up_addr = PGROUNDDOWN(s0) + PGSIZE;
//s0 = *(uint64*)(s0 - 16);
printf("backtrace\n");
while(s0 < up_addr){
printf("%p\n", *(uint64*)(s0 - 8)); //获取该函数的返回地址ra
s0 = *(uint64*)(s0 - 16); //获取上一个函数的帧指针
}
}
r_fp()就是一个读取s0的嵌入汇编。
4. alarm实现
具体要求是提供这样一种对外接口:
uint64 sigalarm(interval, handler);
uint64 sigreturn();
用户调用系统调用signalarm后,要求每经过interval个时钟中断后,调用handler函数,但是要求用户在handler函数的最后必须调用sigreturn来进行返回。且sigalarm(0, 0)语义是取消alarm。
在proc结构中添加如下属性:
interval和handler用于记录用户传入的参数。acc是计数器,当acc>=interval时就调用用户的handler函数。在allocproc中将inerval和handler初始为0,而将acc初始为-1(因为我希望在interval为0时表示用户没有调用闹钟)。
因此sigalarm的实现如下:
uint64
sys_sigalarm(){
int interval;
uint64 handler;
struct proc* p = myproc();
if(argint(0, &interval) < 0){
return -1;
}
if(argaddr(1, &handler) < 0){
return -1;
}
p->interval = interval;
p->handler = (void*)handler;
if(p->interval == 0)p->acc = -1;
else p->acc = 0;
return 0;
}
uint64
sys_sigreturn(){
struct proc* p = myproc();
p->sign = 1;
return 0;
}
sigreturn只是简单地设置一个标志位sign,在usertrapret函数中通过这个标志位来判断用户是否是调用sigreturn而陷入内核。
我们在处理时钟中断的时候进行acc的更新:
usertrapret函数的修改如下:
// send syscalls, interrupts, and exceptions to trampoline.S
w_stvec(TRAMPOLINE + (uservec - trampoline));
if(p->sign == 1){ //第一部分的增加
p->sign = 0;
p->in_handle = 0;
//if(p->interval == 0)p->acc = -1;
//else p->acc = 0;
memmove(p->trapframe, &(p->restore), sizeof(struct trapframe));
}
// set up trapframe values that uservec will need when
// the process next re-enters the kernel.
p->trapframe->kernel_satp = r_satp(); // kernel page table
p->trapframe->kernel_sp = p->kstack + PGSIZE; // process's kernel stack
p->trapframe->kernel_trap = (uint64)usertrap;
p->trapframe->kernel_hartid = r_tp(); // hartid for cpuid()
// set up the registers that trampoline.S's sret will use
// to get to user space.
// set S Previous Privilege mode to User.
unsigned long x = r_sstatus();
x &= ~SSTATUS_SPP; // clear SPP to 0 for user mode
x |= SSTATUS_SPIE; // enable interrupts in user mode
w_sstatus(x);
// set S Exception Program Counter to the saved user pc.
if(p->acc >= p->interval && p->in_handle == 0){ //第二部分增加
w_sepc((uint64)p->handler);
memmove(&(p->restore), p->trapframe, sizeof(struct trapframe));
//printf("现在acc:%d,现在interval: %d\n", p->acc, p->interval);
//p->acc = 0;
p->acc = 0;
p->in_handle = 1;
}
else{
//printf("我进入了这里\n");
w_sepc(p->trapframe->epc);
}
// tell trampoline.S the user page table to switch to.
uint64 satp = MAKE_SATP(p->pagetable);
可以看到,主要增加了两个if判断的语句。首先看第二部分的增加,这部分用来判断是否需要进入handler函数。in_handle标识位的作用在于判断该进程是否处于handler(防止hanlder的重入)。如果acc超过了interval,则设置返回pc为handler,同时保存进程的trapframe。然后把acc清0同时设置in_handle为1(表明进程接下来会处于handler)。
第一部分主要做handler的退出工作,设置in_handler=0,然后恢复进程原来的trapframe(当然接下来不一定能回到进程原来的位置,因为有可能handler的执行时间超过interval个时钟中断,进程又回到handler中)。
主要的工作差不多就这些了吧。