Trap (陷入/中斷) 原始碼解析

zhushoucheng發表於2021-11-04

使用者空間和核心空間之間的切換通常稱為trap

trap的三種形式
  1. 系統呼叫引發
  2. 異常發生
  3. 裝置中斷 (時間中斷、IO中斷、網路中斷等)
supervise mode的許可權

使用者態和核心態之間的到底有什麼區別?其實區別很小:

其中的一件事情是,你現在可以讀寫控制暫存器了。比如說,當你在supervisor mode時,你可以:讀寫SATP暫存器,也就是page table的指標;STVEC,也就是處理trap的核心指令地址;SEPC,儲存當發生trap時的程式計數器;SSCRATCH等等。在supervisor mode你可以讀寫這些暫存器,而使用者程式碼不能做這樣的操作。

另一件事情supervisor mode可以做的是,它可以使用PTE_U標誌位為0的PTE(頁表項 詳見頁表)。當PTE_U標誌位為1的時候,表明使用者程式碼可以使用這個頁表;如果這個標誌位為0,則只有supervisor mode可以使用這個頁表。

這也是supervise mode唯二可以做的事情了。

ecall指令

系統使用ecall指令來執行系統呼叫

第一,ecall將程式碼從user mode改到supervisor mode

第二,ecall將程式計數器的值儲存在了SEPC暫存器。

第三,ecall會跳轉到STVEC暫存器指向的指令 即trampoline.S

trap的全過程

exec系統呼叫為例。

首先,使用者程式中呼叫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;
  }
}

相關文章