Skip to content

Latest commit

 

History

History
149 lines (128 loc) · 5.09 KB

File metadata and controls

149 lines (128 loc) · 5.09 KB

Trap Handling

When a user-space program triggers a system call, it needs to switch from user mode to kernel mode. This process involves three main stages:

  1. When the application enters the kernel via an ecall instruction, the operating system saves the trap context of the interrupted application.
  2. The operating system dispatches and processes the system call based on the contents of trap-related CSR registers.
  3. After completing the system call, the operating system restores the application's trap context and uses the sret instruction to resume its execution.

The trap handling flow is illustrated below:

!Trap Handling Flow

Let's examine these three stages in detail.

Stage 1: Saving the Context

Application System Calls

When an application executes any of the following system call functions, it triggers a kernel trap, and the kernel saves the application's trap context.

/// Writes data from a buffer in memory to a file.
pub fn sys_write(fd: usize, buffer: &[u8]) -> isize;
/// Exits the application and reports a return code to the batch system.
pub fn sys_exit(xstate: i32) -> !;
/// Actively yields CPU ownership to switch to another application.
pub fn sys_yield() -> isize;
/// Gets the current time.
pub fn sys_get_time() -> isize;
/// Creates a new child process.
pub fn sys_fork() -> isize;
/// Clears the current process's address space and loads a specific executable.
pub fn sys_exec(path: &str) -> isize;
/// Waits for a child process to become a zombie, reclaims its resources, and collects its exit code.
pub fn sys_waitpid(pid: isize, exit_code: *mut i32) -> isize;
/// Gets the process ID (PID).
pub fn sys_getpid() -> isize;
/// Reads content from a file into a buffer.
pub fn sys_read(fd: usize, buffer: &mut [u8]) -> isize;
/// Opens a regular file and returns a file descriptor.
pub fn sys_open(path: &str, flags: u32) -> isize;
/// Closes a file descriptor.
pub fn sys_close(fd: usize) -> isize;

TrapContext Data Structure

The TrapContext struct stores all the necessary state to resume an application after a trap.

pub struct TrapContext {
  /// General-purpose registers x0-x31
  pub x: [usize; 32],
  /// CSR sstatus
  pub sstatus: Sstatus,
  /// CSR sepc: The instruction to return to after the trap
  pub sepc: usize,
  /// Token for the kernel address space (physical address of the kernel page table root)
  pub kernel_satp: usize,
  /// Virtual address of the current application's kernel stack pointer
  pub kernel_sp: usize,
  /// Virtual address of the trap handler entry point in the kernel
  pub trap_handler: usize,
}

Saving the Trap Context

The __alltraps function is executed to save the application's trap context, preserving its environment before the system call.

__alltraps:
    # Swap sp with sscratch. `sp` now points to the kernel stack,
    # and `sscratch` holds the user stack pointer.
    csrrw sp, sscratch, sp
    # Save general-purpose registers to the kernel stack
    sd x1, 1*8(sp)
    # Skip x2 (sp), which will be saved later, and x4 (tp)
    sd x3, 3*8(sp)
    # Use .rept to save registers x5 to x31
    .set n, 5
    .rept 27
        SAVE_GP %n
        .set n, n+1
    .endr
    # Read sstatus and sepc CSRs into temporary registers
    csrr t0, sstatus
    csrr t1, sepc
    # Save them to the kernel stack
    sd t0, 32*8(sp)
    sd t1, 33*8(sp)
    # Read the original user stack pointer from sscratch and save it
    csrr t2, sscratch
    sd t2, 2*8(sp)
    # Load kernel space token (SATP) and trap handler address from the TrapContext
    ld t0, 34*8(sp) # kernel_satp
    ld t1, 36*8(sp) # trap_handler
    # Set sp to the top of the application's kernel stack
    ld sp, 35*8(sp) # kernel_sp
    # Switch to the kernel's address space
    csrw satp, t0
    # Flush the TLB
    sfence.vma
    # Jump to the Rust trap handler
    jr t1

Stage 2: Handling the Trap

The trap_handler() function in Rust processes the trap (e.g., an interrupt or system call). It reads the system call number and arguments from the saved TrapContext and dispatches to the appropriate handler function.

pub fn trap_handler() -> !;

Stage 3: Restoring the Context

After the system call is processed, the __restore function is called to load the saved TrapContext and return to user mode.

__restore:
    # a0: Virtual address of the TrapContext in the application's address space
    # a1: Token (SATP value) for the application's address space
    # Switch to the user address space
    csrw satp, a1
    # Flush the TLB
    sfence.vma
    # Store the address of the TrapContext in sscratch for the next trap
    csrw sscratch, a0
    # Set sp to point to the TrapContext to begin restoring registers
    mv sp, a0
    # Restore sstatus and sepc CSRs
    ld t0, 32*8(sp)
    ld t1, 33*8(sp)
    csrw sstatus, t0
    csrw sepc, t1
    # Restore general-purpose registers (except x0, x2, x4)
    ld x1, 1*8(sp)
    ld x3, 3*8(sp)
    .set n, 5
    .rept 27
        LOAD_GP %n
        .set n,n+1
    .endr
    # Restore the user stack pointer into sp
    ld sp, 2*8(sp)
    # Return to User mode to continue application execution
    sret