💈Traps
00 分钟
2023-7-20
2023-8-13
type
status
date
slug
summary
tags
category
icon
password
Property
Aug 13, 2023 04:24 AM

Traps and Sytem calls

There are three kinds of event which cause the CPU to set aside ordinary execution of instructions and force a transfer of control to special code that handles the event. We use trap as a generic term for these situations
  • system call: when a suer program executes the ecall instruction to ask the kernel to do something for it
  • exception: an instruction(user or kernel) does something illegal, such as divide by zero or use an invalid virtual address
  • device interrupt: when a device signals that in needs attention, for example when disk shouldn’t need to be aware that anything write request
  • Trap is transparent
Trap’s process
  1. A trap forces a transfer of control into the kernel
  1. The kernel saves registers and other state so that execution can be resumed
  1. The kernel execute approprate handler code
  1. The kernel restores the saved state and returns from the trap
  1. The original code resumes where it left off
  • Xv6 handles all traps in the kernel, which means traps are not delivered to user code
      1. Handling traps in the kernel is natural for system calls.
      1. It makes sense for interrupts since isolation demands that only the kernel be allowed to use devices, and because the kernel is a convenient mechanism with which to share devices among multiple processes.
      1. It also makes sense for exceptions since xv6 responds to all exceptions from user space by killing the offending program
  • Four stages Xv6 trap handling proceeds
      1. Hardware actions taken by the RISC-V CPU (stvec will be set in this step)
      1. Some assembly instructions that prepare the way for kernel C code
      1. A C function that decides what to do with the trap(usertrap() and kerneltrap())
      1. The system call or device-driver service routine
  • Three distinct cases:
      1. traps from user space
      1. traps from kernel space
      1. timer interrupts
💡
Kernel code (assembler or C) that processes a trap is often called a handler; the first handler instructions are usually written in assembler (rather than C) and are sometimes called a vector.

RISC-V trap machinery

  • some inportant registers
    • sscratch: The kernel places a value here that comes in handy at the very start of a trap handler. (trapframe is put here in kernal)
    • scause: A number that describes the reason for the trap will be put here .
    • stvec: Address of its trap handler; the RISC-V jumps to the address in stvec to handle a trap. stvec → pc
    • sepc: PC will be saved here (since the pc is then overwritten with the value in stvec) when a trap occurs. The sret (return from trap) instruction copies sepc to the pc. The kernel can write sepc to control where sret goes. pc → sepc
    • sstatus:
      • SIE: Control whether device interrupts are enabled. If the kernel clears SIE, the RISC-V will defer device interrupts until the kernel sets SIE.
      • SPP: Indicate whether a trap came from user mode or supervisor mode, and controls to what mode sret returns.
      • notion image
    • satp: holds the address of the pgtbl root
  • The RISC-V hardware does the following for all trap types when it needs to force a trap
      1. If the trap is a device interrupt, and the sstatus SIE bit is clear, don’t do any of the following.
      1. Disable interrupts by clearing the SIE bit in sstatus.
      1. Copy the pc to sepc
      1. Save the current mode (user or supervisor) in the SPP bit in sstatus.
      1. Set scause to reflect the trap’s cause
      1. Set the mode to supervisor.
      1. Copy stvec to the pc.
      1. Start executing at the new pc.
💡
Note that CPU doesn’t switch to the kernel pgtbl, neither a stack in kernel, and doesn’t save any registers except the PC, which should be done by kernel software.

Traps from user space

💡
A trap may occur while executing in user space if the user program makes a system call (ecall instruction), or does something illegal, or if a device interrupts.
  • when a trap occurs, which code blocks shoud be executed
    • uservec(kernel/trampoline.S:16): The high-level path of a trap from user space
    • usertrap(kernel/trap.c:37):
    • usertrapret(kernel/trap.c:90): when returning
    • userret(kernel/trampoline.S:88):
  • Trampoline:
    • It is mapped in the user page table, with the PTE_U flag, traps can start executing there in su- pervisor mode.
    • It is mapped at the same address in the kernel address space, the trap handler can continue to execute after it switches to the kernel page table.
If the trap is a system call, usertrap calls syscall to handle it; if a device interrupt, devintr; otherwise it’s an exception, and the kernel kills the faulting process.

Traps from kernel space

  • what should kernel do before handling the trap?
    • set stvec points to kernelvec
    • satp points to the kernel pgtbl
    • stack point points to a vaild kernel stack
  • when a trap occurs, what code blocks should be executed?
      1. kernelvec copys the 32 registers on the stack, and jump to kerneltrap()
      1. kerneltrap() executes trap handling, and returns to kernelvec
      1. kernelvec() set the 32 registers from stack
      kernelvec copys the 32 registers on the stack of interrupted kernel thread

code

 
  • usertrap()
    • Determine the cause of the trap, process it, and return
    • Changes stvec so that a trap while in the kernel will be handled by kernelvec rather than uservec
    • It saves the sepc register (the saved user PC), because usertrap might call yield to switch to another process’s kernel thread, and that process might return to user space, in the process of which it will modify sepc.
      1. 首先检查trap是否来自user mode
      1. 改变stvec值指向kernelvec而不是userevec,从而将后续trap类型为interrupts和exception交给kerneltrap()
      1. 保存spec中的user pc,usertrap可能会调用yield(),改变到另一个process‘s kernel
      1. 判断trap的类型,syscall则交给syscall()处理,否则就是异常,kernel杀死该进程
      1. syscall()执行前将trapframe→epc += 4
      1. usertrap()结束之前,检查该进程是否被杀死,如果是timer interruption需要让出cpu
  • kerneltrap()
    • 首先检查sstatus的SPP位,确认trap来自supervisor mode,检查SIE
    • 调用devintr(),检查是timer interrupt,device interrupt或者exception
      • device interrupt:调用uartintr()或virtio_disk_inte()进行进一步的处理
      • time interrupt:return 2,调用yield()
      • 无法识别:返回0并panic
    • 当等到CPU有机会运行我们的thread,则会继续运行kerneltrap()
      • kerneltrap()结束后,需要返回之前,需要恢复spec和sstatus,这两个寄存器可能以为yield()导致里边存放的是脏值。然后返回到kernelvec汇编代码
      • 最后返回到kernnelvec,恢复并返回the interrupted kernel code
  • usertrapret()
    • 用于返回用户空间
      1. 准备工作,关闭中断
      1. 设置stvec指向uservec
      1. 为process将来的trap设置好trapframe,恢复pgtbl,stack,hartid等
      1. spp设置为0,恢复中断
      1. p→trapframe = user pc
      1. 调用uerret
  • uservec
    • The csrrw instruction at the start of uservec swaps the contents of a0 and sscratch. Now the user code’s a0 is saved in sscratch; uservec has one register (a0) to play with(a0 is avaliable now); and a0 contains the value the kernel previously placed in sscratch.
    • Save the 32 user registers. Before entering user space, the kernel set sscratch to point to a per-process trapframe structure that (among other things) has space to save the 32 user registers
      1. save 32 general registers
      1. load the kernel’s pgtbl, stack, current cpu id into relavent registers
      1. jump to usertrap() and execute the c code
  • sret
    • 与ecall刚好相反
      1. enable int
      1. copy sepc to pc
      1. set mode to usermode
      1. start executing at the new pc
  • userret
    • 主要完成从kernel到user的完整切换
      1. 传入的参数中,a1存放指向process‘s用户态页表的指针,首先交换a1和satp,调用汇编指令切换到user pgtbl
      1. a0存放TRAPFRAME,将a0值存在t0,然后存到sscratch中
      1. 恢复现场,从trapframe中恢复除了a0之外所有寄存器的值
      1. 交换sscratch和a0的值,恢复a0,将trapframe保存到sscratch,为下一次的trap做好准备
      1. 执行sret指令
  • ecall
      1. 如果trap是设备中断,且SIE位被清空,则不执行任何指令
      1. 清除SIE位以禁止中断
      1. U mode → S mode
      1. spec = pc
      1. pc = stvec
      1. jump to pc
      1. 将当前的mode保存在SPP中
 

评论
Loading...