预备知识:
陷阱(trap):
有三种事件会导致中央处理器搁置普通指令的执行,并强制将控制权转移到处理该事件的特殊代码上。一种情况是系统调用,当用户程序执行ecall
指令要求内核为其做些什么时;另一种情况是异常:(用户或内核)指令做了一些非法的事情,例如除以零或使用无效的虚拟地址;第三种情况是设备中断,一个设备,例如当磁盘硬件完成读或写请求时,向系统表明它需要被关注。、
用户空间:
来自用户空间的陷阱的高级路径是uservec
(kernel/trampoline.S:16),然后是usertrap
(kernel/trap.c:37);返回时,先是usertrapret
(kernel/trap.c:90),然后是userret
(kernel/trampoline.S:16)。
系统调用流程:
用户调用是如何在内核中实现exec
系统调用的。
用户代码将exec
需要的参数放在寄存器a0
和a1
中,并将系统调用号放在a7
中。系统调用号与syscalls
数组中的条目相匹配,syscalls
数组是一个函数指针表(kernel/syscall.c:108)。ecall
指令陷入(trap)到内核中,执行uservec
、usertrap
和syscall
,和我们之前看到的一样。
syscall
(kernel/syscall.c:133)从陷阱帧(trapframe)中保存的a7
中检索系统调用号(p->trapframe->a7
),并用它索引到syscalls
中,对于第一次系统调用,a7
中的内容是SYS_exec
(kernel/syscall. h:8),导致了对系统调用接口函数sys_exec
的调用。
当系统调用接口函数返回时,syscall
将其返回值记录在p->trapframe->a0
中。这将导致原始用户空间对exec()
的调用返回该值,因为RISC-V上的C调用约定将返回值放在a0
中。系统调用通常返回负数表示错误,返回零或正数表示成功。如果系统调用号无效,syscall
打印错误并返回-1。
Backtrace:
void
backtrace(void) {
printf("backtrace:\n");
// 读取当前帧指针
uint64 fp = r_fp();
while (PGROUNDUP(fp) - PGROUNDDOWN(fp) == PGSIZE) {
// 返回地址保存在-8偏移的位置
uint64 ret_addr = *(uint64*)(fp - 8);
printf("%p\n", ret_addr);
// 前一个帧指针保存在-16偏移的位置
fp = *(uint64*)(fp - 16);
}
}
帧指针指向栈帧的栈顶,帧指针-8就是指向当前函数返回地址,-16就是指向上一个函数的帧指针。每个kernel stack都是一页,因此可以通过计算PGROUNDDOWN(fp)
和PGROUNDUP(fp)
来确定当前的fp地址是否还位于这一页内,从而可以是否已经完成了所有嵌套的函数调用的backtrace。
Alarm:
实验要求:添加一个新的system callsigalarm(interval, handler)
,interval
是一个计时器的tick的间隔大小,handler
是指向一个函数的指针,这个函数是当计时器tick到达interval
时触发的函数。
首先修改makefile:
然后在user/user.h中添加函数声明,系统调用用户接口
int sigalarm(int ticks, void (*handler)());
int sigreturn(void);
在user/user,pl中添加 entry入口
在kernel/syscall.h中添加 系统调用号
在kernel/syscall.c中添加 注册系统调用
在kernel/proc.h中添加结构体成员
int interval; //定时器出发周期
void (*handler)(); // 指向函数得指针
int ticks; // 计时器
int in_handler; //记录是否还在handler函数中
uint64 saved_epc;
uint64 saved_ra;
uint64 saved_sp;
uint64 saved_gp;
uint64 saved_tp;
uint64 saved_t0;
uint64 saved_t1;
uint64 saved_t2;
uint64 saved_s0;
uint64 saved_s1;
uint64 saved_s2;
uint64 saved_s3;
uint64 saved_s4;
uint64 saved_s5;
uint64 saved_s6;
uint64 saved_s7;
uint64 saved_s8;
uint64 saved_s9;
uint64 saved_s10;
uint64 saved_s11;
uint64 saved_a0;
uint64 saved_a1;
uint64 saved_a2;
uint64 saved_a3;
uint64 saved_a4;
uint64 saved_a5;
uint64 saved_a6;
uint64 saved_a7;
uint64 saved_t3;
uint64 saved_t4;
uint64 saved_t5;
uint64 saved_t6;
在kernel/sysfile.c中实现具体函数
sys_sigalarm将userfunction传入得两个参数分别保存到p结构体相应的成员变量中
uint64
sys_sigalarm(void)
{
int ticks;
uint64 funaddr; // pointer to function
if ((argint(0, &ticks) < 0) || (argaddr(1, &funaddr) < 0)) {
return -1;
}
struct proc *p = myproc();
p->interval = ticks;
p->handler = (void(*)())funaddr;
return 0;
}
sys_sigreturn会在handler函数完成后被调用,注意工作就是恢复现场得寄存器,in_handler置09
uint64
sys_sigreturn(void)
{
struct proc *p = myproc();
p->trapframe->epc = p->saved_epc;
p->trapframe->ra = p->saved_ra;
p->trapframe->sp = p->saved_sp;
p->trapframe->gp = p->saved_gp;
p->trapframe->tp = p->saved_tp;
p->trapframe->t0 = p->saved_t0;
p->trapframe->t1 = p->saved_t1;
p->trapframe->t2 = p->saved_t2;
p->trapframe->t3 = p->saved_t3;
p->trapframe->t4 = p->saved_t4;
p->trapframe->t5 = p->saved_t5;
p->trapframe->t6 = p->saved_t6;
p->trapframe->s0 = p->saved_s0;
p->trapframe->s1 = p->saved_s1;
p->trapframe->s2 = p->saved_s2;
p->trapframe->s3 = p->saved_s3;
p->trapframe->s4 = p->saved_s4;
p->trapframe->s5 = p->saved_s5;
p->trapframe->s6 = p->saved_s6;
p->trapframe->s7 = p->saved_s7;
p->trapframe->s8 = p->saved_s8;
p->trapframe->s9 = p->saved_s9;
p->trapframe->s10 = p->saved_s10;
p->trapframe->s11 = p->saved_s11;
p->trapframe->a0 = p->saved_a0;
p->trapframe->a1 = p->saved_a1;
p->trapframe->a2 = p->saved_a2;
p->trapframe->a3 = p->saved_a3;
p->trapframe->a4 = p->saved_a4;
p->trapframe->a5 = p->saved_a5;
p->trapframe->a6 = p->saved_a6;
p->trapframe->a7 = p->saved_a7;
p->in_handler = 0;
return 0;
}
修改kernel/trap.c,usertrap()是对中断定时器处理,当p->ticks到达预定值p->interval,将调用p->handler,这是通过向p->trapframe->epc加载handler地址实现的,这样当从usertrap返回时,pc恢复现场时会加载epc,就会跳转到handler地址,跳转前保存好寄存器。
else if((which_dev = devintr()) != 0){
// ok
if (which_dev == 2 && p->in_handler == 0) {
p->ticks += 1;
if ((p->ticks == p->interval) && (p->interval != 0)) {
p->in_handler = 1;
p->ticks = 0;
p->saved_epc = p->trapframe->epc;
p->saved_ra = p->trapframe->ra;
p->saved_sp = p->trapframe->sp;
p->saved_gp = p->trapframe->gp;
p->saved_tp = p->trapframe->tp;
p->saved_t0 = p->trapframe->t0;
p->saved_t1 = p->trapframe->t1;
p->saved_t2 = p->trapframe->t2;
p->saved_t3 = p->trapframe->t3;
p->saved_t4 = p->trapframe->t4;
p->saved_t5 = p->trapframe->t5;
p->saved_t6 = p->trapframe->t6;
p->saved_s0 = p->trapframe->s0;
p->saved_s1 = p->trapframe->s1;
p->saved_s2 = p->trapframe->s2;
p->saved_s3 = p->trapframe->s3;
p->saved_s4 = p->trapframe->s4;
p->saved_s5 = p->trapframe->s5;
p->saved_s6 = p->trapframe->s6;
p->saved_s7 = p->trapframe->s7;
p->saved_s8 = p->trapframe->s8;
p->saved_s9 = p->trapframe->s9;
p->saved_s10 = p->trapframe->s10;
p->saved_s11 = p->trapframe->s11;
p->saved_a0 = p->trapframe->a0;
p->saved_a1 = p->trapframe->a1;
p->saved_a2 = p->trapframe->a2;
p->saved_a3 = p->trapframe->a3;
p->saved_a4 = p->trapframe->a4;
p->saved_a5 = p->trapframe->a5;
p->saved_a6 = p->trapframe->a6;
p->saved_a7 = p->trapframe->a7;
p->trapframe->epc = (uint64)p->handler;
}
}
}