Trap (陷入/中断) 源码解析

2021年11月20日 阅读数:4
这篇文章主要向大家介绍Trap (陷入/中断) 源码解析,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

用户空间和内核空间之间的切换一般称为traphtml

trap的三种形式
  1. 系统调用引起
  2. 异常发生
  3. 设备中断 (时间中断、IO中断、网络中断等)
supervise mode的权限

用户态和内核态之间的到底有什么区别?其实区别很小:网络

其中的一件事情是,你如今能够读写控制寄存器了。好比说,当你在supervisor mode时,你能够:读写SATP寄存器,也就是page table的指针;STVEC,也就是处理trap的内核指令地址;SEPC,保存当发生trap时的程序计数器;SSCRATCH等等。在supervisor mode你能够读写这些寄存器,而用户代码不能作这样的操做。app

另外一件事情supervisor mode能够作的是,它能够使用PTE_U标志位为0的PTE(页表项 详见页表)。当PTE_U标志位为1的时候,代表用户代码能够使用这个页表;若是这个标志位为0,则只有supervisor mode能够使用这个页表。函数

这也是supervise mode惟二能够作的事情了。ui

ecall指令

系统使用ecall指令来执行系统调用this

第一,ecall将代码从user mode改到supervisor modespa

第二,ecall将程序计数器的值保存在了SEPC寄存器。操作系统

第三,ecall会跳转到STVEC寄存器指向的指令 即trampoline.S指针

trap的全过程

exec系统调用为例。rest

首先,用户程序中调用exec函数,编译器解析exec函数时,并不会调用其源码,而是将exec系统调用的编号写入A7寄存器中,执行ecall指令,ecall指令将跳转到trampoline.S。

系统从用户态进入了内核态,但目前页表依旧是user page table。trampoline.S主要是使用trapFrame(附录部分)保存了系统的状态以及用户寄存器,用于恢复用户进程,经过这个操做也实现用户进程对于进程切换的无感知。在将须要存储的状态存入user page table后,系统将切换内核页表,执行相应的内核代码,即usertrap函数。

usertrap根据trap的不一样状况(scause寄存器中存储着中断缘由),调用相应的处理函数。

代码解析

下面的代码是操做系统的源代码,里面包含了不少的细节,你须要里了解的是整个流程、体系,不要过多的关注细节

trap的保存流程
  1. 首先,咱们须要保存32个用户寄存器。由于很显然咱们须要恢复用户应用程序的执行,尤为是当用户程序随机的被设备中断所打断时。(存储用户寄存器)
  2. 程序计数器也须要在某个地方保存,它几乎跟一个用户寄存器的地位是同样的,咱们须要可以在用户程序运行中断的位置继续执行用户程序。(存储一些系统寄存器 如PC)
  3. 咱们须要将mode改为supervisor mode,由于咱们想要使用内核中的各类各样的特权指令。(修改运行模式)
  4. SATP(Supervisor Address Translation and Protection,存储了根页表的地址)寄存器如今正指向user page table,而user page table只包含了用户程序所须要的内存映射和一两个其余的映射,它并无包含整个内核数据的内存映射。因此在运行内核代码以前,咱们须要将SATP指向kernel page table。(修改SATP,得到内核的页表)
  5. 咱们须要将堆栈寄存器指向位于内核的一个地址,由于咱们须要一个堆栈来调用内核的C函数。(转换到内核堆栈)
  6. 一旦咱们设置好了,而且全部的硬件状态都适合在内核中使用, 咱们须要跳入内核的C代码usertrap函数。
trampoline.S
	#
        # code to switch between user and kernel space.
        #
        # this code is mapped at the same virtual address
        # (TRAMPOLINE) in user and kernel space so that
        # it continues to work when it switches page tables.
	#
	# kernel.ld causes this to be aligned
        # to a page boundary.
        #
	.section trampsec
	.globl trampoline
	trampoline:
	.align 4
	.globl uservec
	//trap状态保存
	uservec:    
        # trap.c sets stvec to point here, so
        # traps from user space start here,
        # in supervisor mode, but with a
        # user page table.
        #
        # sscratch points to where the process's p->trapframe is
        # mapped into user space, at TRAPFRAME.
        #
        
			# swap a0 and sscratch
        # so that a0 is TRAPFRAME
        csrrw a0, sscratch, a0

        # save the user registers in TRAPFRAME
        sd ra, 40(a0)
        sd sp, 48(a0)
        sd gp, 56(a0)
        sd tp, 64(a0)
        sd t0, 72(a0)
        sd t1, 80(a0)
        sd t2, 88(a0)
        sd s0, 96(a0)
        sd s1, 104(a0)
        sd a1, 120(a0)
        sd a2, 128(a0)
        sd a3, 136(a0)
        sd a4, 144(a0)
        sd a5, 152(a0)
        sd a6, 160(a0)
        sd a7, 168(a0)
        sd s2, 176(a0)
        sd s3, 184(a0)
        sd s4, 192(a0)
        sd s5, 200(a0)
        sd s6, 208(a0)
        sd s7, 216(a0)
        sd s8, 224(a0)
        sd s9, 232(a0)
        sd s10, 240(a0)
        sd s11, 248(a0)
        sd t3, 256(a0)
        sd t4, 264(a0)
        sd t5, 272(a0)
        sd t6, 280(a0)

				# save the user a0 in p->trapframe->a0
        csrr t0, sscratch
        sd t0, 112(a0)

        # restore kernel stack pointer from p->trapframe->kernel_sp
        ld sp, 8(a0)

        # make tp hold the current hartid, from p->trapframe->kernel_hartid
        ld tp, 32(a0)

        # load the address of usertrap(), p->trapframe->kernel_trap
        ld t0, 16(a0)

        # restore kernel page table from p->trapframe->kernel_satp
        ld t1, 0(a0)
        csrw satp, t1
        sfence.vma zero, zero

        # a0 is no longer valid, since the kernel page
        # table does not specially map p->tf.

        # jump to usertrap(), which does not return
        jr t0
		//trap状态恢复
		.globl userret
		userret:
        # userret(TRAPFRAME, pagetable)
        # 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.
        csrw satp, a1
        sfence.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
        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 sscratch
        csrrw a0, sscratch, a0
        
        # return to user mode and user pc.
        # usertrapret() set up sstatus and sepc.
        sret

用户trap处理程序
void
usertrap(void)
{
  int which_dev = 0;
	
  //判断系统mode状态
  if((r_sstatus() & SSTATUS_SPP) != 0)
    panic("usertrap: not from user mode");

  // send interrupts and exceptions to kerneltrap(),
  // since we're now in the kernel.
  // 将中断处理程序设置为kernelvec
  // 即将kernelvec函数地址写入到stvec寄存器
  // 和user model下的中断对应,kernelvec用来处理内核态下的中断
  // 两个流程很类似,kernelvec更简单一些,就不作解释了
  w_stvec((uint64)kernelvec);

  struct proc *p = myproc();
  
  //保存用户的PC值
  // save user program counter.
  p->trapframe->epc = r_sepc();
  
  //根据scause寄存器的值判断异常缘由
  if(r_scause() == 8){
    // system call

    if(p->killed)
      exit(-1);

    // sepc points to the ecall instruction,
    // but we want to return to the next instruction.
    // 设置返回位置
    p->trapframe->epc += 4;
		
    // an interrupt will change sstatus &c registers,
    // so don't enable until done with those registers.
    // 寄存器保存完成,开中断
    intr_on();
		// 执行系统调用
    syscall();
  } else if((which_dev = devintr()) != 0){
    // 处理设备中断
    // ok
  } else {
    printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid);
    printf("            sepc=%p stval=%p\n", 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)
    yield();
	
  //trap处理完毕,恢复用户进程状态
  usertrapret();
}

附:

系统调用函数 (根据A7寄存器存储的内容,调用不一样的函数指针)

extern uint64 sys_chdir(void);
extern uint64 sys_close(void);
extern uint64 sys_dup(void);
extern uint64 sys_exec(void);
extern uint64 sys_exit(void);
extern uint64 sys_fork(void);
extern uint64 sys_fstat(void);
extern uint64 sys_getpid(void);
extern uint64 sys_kill(void);
extern uint64 sys_link(void);
extern uint64 sys_mkdir(void);
extern uint64 sys_mknod(void);
extern uint64 sys_open(void);
extern uint64 sys_pipe(void);
extern uint64 sys_read(void);
extern uint64 sys_sbrk(void);
extern uint64 sys_sleep(void);
extern uint64 sys_unlink(void);
extern uint64 sys_wait(void);
extern uint64 sys_write(void);
extern uint64 sys_uptime(void);

static uint64 (*syscalls[])(void) = {
[SYS_fork]    sys_fork,
[SYS_exit]    sys_exit,
[SYS_wait]    sys_wait,
[SYS_pipe]    sys_pipe,
[SYS_read]    sys_read,
[SYS_kill]    sys_kill,
[SYS_exec]    sys_exec,
[SYS_fstat]   sys_fstat,
[SYS_chdir]   sys_chdir,
[SYS_dup]     sys_dup,
[SYS_getpid]  sys_getpid,
[SYS_sbrk]    sys_sbrk,
[SYS_sleep]   sys_sleep,
[SYS_uptime]  sys_uptime,
[SYS_open]    sys_open,
[SYS_write]   sys_write,
[SYS_mknod]   sys_mknod,
[SYS_unlink]  sys_unlink,
[SYS_link]    sys_link,
[SYS_mkdir]   sys_mkdir,
[SYS_close]   sys_close,
};

void
syscall(void)
{
  int num;
  struct proc *p = myproc();

  num = p->trapframe->a7;
  if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
    p->trapframe->a0 = syscalls[num]();
  } else {
    printf("%d %s: unknown sys call %d\n",
            p->pid, p->name, num);
    p->trapframe->a0 = -1;
  }
}