本文主要是介绍xv6源码解析 014,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
xv6源码解析 014
今天我们就来看看当进程从用户态陷入的时候是如何返回用户空间的,争取今天结束trap机制。明天开文件系统,五一假期争取结束这个专题。
话不多说我们直接开始。
我们先复习一下usertrap的整个过程。
我们正式开始
在usertrap的结尾是会调用这样一个函数
void
usertrap(void)
{...usertrapret();
}
这是一个不会返回的函数,正如我们上篇说到的,在trap的整个过程链中的函数都是不会返回的。
void usertrapret()
void
usertrapret(void)
{struct proc *p = myproc();// we're about to switch the destination of traps from// kerneltrap() to usertrap(), so turn off interrupts until// we're back in user space, where usertrap() is correct.// 屏蔽中断,在陷入或者返回的过程中都不允许中断产生intr_off();// send syscalls, interrupts, and exceptions to trampoline.Sw_stvec(TRAMPOLINE + (uservec - trampoline));// set up trapframe values that uservec will need when// the process next re-enters the kernel.// 保存寄存器的状态到tramframe中,以备下一次trapp->trapframe->kernel_satp = r_satp(); // kernel page tablep->trapframe->kernel_sp = p->kstack + PGSIZE; // process's kernel stackp->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.// 切换cpu的状态unsigned long x = r_sstatus();x &= ~SSTATUS_SPP; // clear SPP to 0 for user modex |= SSTATUS_SPIE; // enable interrupts in user modew_sstatus(x);// set S Exception Program Counter to the saved user pc.// 恢复陷入之前的程序计数器的值w_sepc(p->trapframe->epc);// tell trampoline.S the user page table to switch to.// 切换回用户页表uint64 satp = MAKE_SATP(p->pagetable);// jump to trampoline.S at the top of memory, which // switches to the user page table, restores user registers,// and switches to user mode with sret.// 计算出userret的汇编代码段在trampoline中的起始地址uint64 fn = TRAMPOLINE + (userret - trampoline);// 执行userret((void (*)(uint64,uint64))fn)(TRAPFRAME, satp);
}
结合注释来看,函数很好理解,大致可以概括为:关闭中断 -> 保存内核陷入时的状态,为下一次陷入做准备 -> 将cpu从特权模式切换到用户模式 -> 恢复陷入前的程序计数器的值 -> 切换回用户态页表 -> 计算出并执行userret()
汇编函数。
同样userret()
也是不会返回的,我们现在就来看看。
userrtet(TRAPFRAME, pagetable)
userret()会接收两个参数,一个是tramframe paged的地址,另一个是页表的地址
.globl userret
userret:# userret(TRAPFRAME, pagetable)# 分别对应参数寄存器a1 和 a2# switch from kernel to user.# usertrapret() calls here.# a0: TRAPFRAME, in user page table.# a1: user page table, for satp.# switch to the user page table.# 原子的a1寄存器中的值复制到satp中csrw satp, a1sfence.vma zero, zero# put the saved user a0 in sscratch, so we# can swap it with our a0 (TRAPFRAME) in the last step.ld t0, 112(a0)csrw sscratch, t0# restore all but a0 from TRAPFRAME# 将先前保存在tramframe中的寄存器的状态都加载会寄存器中ld ra, 40(a0)ld sp, 48(a0)ld gp, 56(a0)ld tp, 64(a0)ld t0, 72(a0)ld t1, 80(a0)ld t2, 88(a0)ld s0, 96(a0)ld s1, 104(a0)ld a1, 120(a0)ld a2, 128(a0)ld a3, 136(a0)ld a4, 144(a0)ld a5, 152(a0)ld a6, 160(a0)ld a7, 168(a0)ld s2, 176(a0)ld s3, 184(a0)ld s4, 192(a0)ld s5, 200(a0)ld s6, 208(a0)ld s7, 216(a0)ld s8, 224(a0)ld s9, 232(a0)ld s10, 240(a0)ld s11, 248(a0)ld t3, 256(a0)ld t4, 264(a0)ld t5, 272(a0)ld t6, 280(a0)# restore user a0, and save TRAPFRAME in sscratchcsrrw a0, sscratch, a0# return to user mode and user pc.# usertrapret() set up sstatus and sepc.sret
现在我们就安全的返回了用户空间了,站在用户进程的视角来看,用户进程并没有察觉时间的流逝(一个比喻,对于(分时操作系统的)用户进程来说,消耗了时间片才被视为是时间的流逝),相当于时间暂停了一下下,当返回时,又重新开始了。
现在我们可以看看从内核陷入了,相比于从用户态陷入,从内核陷入要简单很多,因为本来就处在内核态中。
我们直接看代码
kerneltrap()
void
kerneltrap()
{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");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);
}
很简单,就相当于一个函数调用的过程,为什么这么说呢?如果当前cpu是在处理用户陷入的流程中,此时我们知道在进入usertrap的时候我们就将陷入处理的向量指向这个函数kerneltrap中;如果我们是在内核中处理其他的事情(我暂时想不出有什么其他的事情),此时陷入处理的向量也是指向这个函数,所以如果有一个无论是什么情况引起的trap,我们都会直接跳转到这个函数中进行处理。处理完之后返回,就这么简单哈哈哈。
时间还有很多,顺便在看看时钟中断和设备中断吧。
首先是时钟中断。一般,时钟中断是以软中断(soft interrupt)的形式出现在内核中的。
软中断和硬中断有什么不同呢?直接上gpt
软中断与硬中断的区别:
- 触发源:
- 硬中断(Hardware Interrupt):由外部硬件设备生成,如上述代码中的时钟中断,当系统时钟到达某个预定计数时自动触发。硬中断打断CPU当前正在执行的任务,强制CPU保存当前状态,然后跳转到中断处理程序执行。
- 软中断(Software Interrupt):由软件内部生成,通过执行特定的中断指令(如系统调用)来触发。它们通常用于在用户态和内核态之间切换,或者请求操作系统服务。
- 控制流与上下文:
- 硬中断是异步的,即它们可以在任何时刻发生,不受CPU控制。
- 软中断往往是同步的,发生在程序执行过程中,其执行时机可以预见。
- 处理方式:
- 硬中断处理通常迅速完成必要的即时操作(如记录中断发生),以减少对正常任务的影响,复杂的处理会推迟到软中断(下半部)进行。
- 软中断(也称为上半部和下半部处理模型中的下半部)处理那些非紧急但可能较耗时的任务,可以在更宽松的时间条件下执行,有时可以在多个CPU上并行处理。
- 中断嵌套与屏蔽:
- 硬中断可以被其他硬中断嵌套(取决于硬件设计),并且可以通过中断屏蔽位来控制是否响应新的硬中断。
- 软中断一般不支持嵌套,但同类软中断可以在不同的CPU核心上并行执行。
ok,看代码
void
clockintr()
{acquire(&tickslock);ticks++;wakeup(&ticks);release(&tickslock);
}
设备中断
int
devintr()
{uint64 scause = r_scause();if((scause & 0x8000000000000000L) &&(scause & 0xff) == 9){// this is a supervisor external interrupt, via PLIC.// irq indicates which device interrupted.int irq = plic_claim();// 判断时什么样的中断if(irq == UART0_IRQ){uartintr();} else if(irq == VIRTIO0_IRQ){virtio_disk_intr();} else if(irq){printf("unexpected interrupt irq=%d\n", irq);}// the PLIC allows each device to raise at most one// interrupt at a time; tell the PLIC the device is// now allowed to interrupt again.if(irq)plic_complete(irq);return 1;} else if(scause == 0x8000000000000001L){// software interrupt from a machine-mode timer interrupt,// forwarded by timervec in kernelvec.S.if(cpuid() == 0){clockintr();}// acknowledge the software interrupt by clearing// the SSIP bit in sip.w_sip(r_sip() & ~2);return 2;} else {return 0;}
}
这篇关于xv6源码解析 014的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!