Return by calling sret, which copies sepc to program counter and resumes the interrupted kernel code.
Implementation Details
Kernel points stvec register to kernelvec assmebly code. kernelvec save registers on the same interrupted kernel thread, since all registers belong to that thread.
// interrupts and exceptions from kernel code go here via kernelvec,// on whatever the current kernel stack is.// must be 4-byte aligned to fit in stvec.voidkerneltrap(){int which_dev =0; uint64 sepc =r_sepc(); uint64 sstatus =r_sstatus(); uint64 scause =r_scause();if((sstatus & SSTATUS_SPP) ==0)panic("kerneltrap: not from supervisor mode");if(intr_get()!=0)panic("kerneltrap: interrupts enabled");// check if it’s an external interrupt or software interrupt,// and handle it.// returns 2 if timer interrupt,// 1 if other device,// 0 if not recognized.if((which_dev =devintr()) ==0){printf("scause %p\n", scause);printf("sepc=%p stval=%p\n", r_sepc(), r_stval());panic("kerneltrap"); }// give up the CPU if this is a timer interrupt.if(which_dev ==2&&myproc()!=0&&myproc()->state == RUNNING)yield();// the yield() may have caused some traps to occur,// so restore trap registers for use by kernelvec.S's sepc instruction.w_sepc(sepc);w_sstatus(sstatus);}
Xv6 sets a CPU’s stvec to kernelvec when that CPU enters the kernel from user space.
voidusertrap(void){// send interrupts and exceptions to kerneltrap(),// since we’re now in the kernel.w_stvec((uint64)kernelvec);// an interrupt will change sstatus &c registers,// so don’t enable until done with those registers.intr_on();
Disable Interrupt
There is a small windows, that stvec hasn’t been set. It is critical that no device interrupt happen during that time.
RISC-V always disables interrupts when it starts to take a trap, and xv6 doesn’t enable them again until after it sets stvec.
// start() jumps here in supervisor mode on all CPUs.voidmain(){if(cpuid()==0){consoleinit();printfinit();printf(“\n”);printf(“xv6 kernel is booting\n”);printf(“\n”);kinit(); *// physical page allocator*kvminit(); *// create kernel page table*kvminithart(); *// turn on paging*procinit(); *// process table*trapinit(); *// trap vectors*trapinithart(); // install kernel trap vectorplicinit(); *// set up interrupt controller*
Note: 在设置好kernel trap vector to stvec之后 我们再enable interrupt,这是为了确保stvec的正确性。否则我们的stvec register在中断发生的时候 存的内容不正确可能会导致严重问题。
Set up to take exceptions and traps while in the kernel.
void usertrap(void)
{
// send interrupts and exceptions to kerneltrap(),
// since we’re now in the kernel.
w_stvec((uint64)kernelvec);
// an interrupt will change sstatus &c registers,
// so don’t enable until done with those registers.
intr_on();