Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Context Switch

Context defined in awkernel_lib/src/context.rs is a trait that enables preemptive multitasking. It provides methods to set the stack pointer, entry point, and argument of the context as follows.

#![allow(unused)]
fn main() {
pub trait Context: Default {
    /// # Safety
    ///
    /// Ensure that changing the stack pointer is valid at that time.
    unsafe fn set_stack_pointer(&mut self, sp: usize);

    /// # Safety
    ///
    /// This function must be called for only initialization purpose.
    unsafe fn set_entry_point(&mut self, entry: extern "C" fn(usize) -> !, arg: usize);

    /// # Safety
    ///
    /// This function must be called for only initialization purpose.
    unsafe fn set_argument(&mut self, arg: usize);
}
}

The context_switch function must be implemented for each architecture to enable context switching.

#![allow(unused)]
fn main() {
extern "C" {
    /// Switch context from `current` to `next`.
    pub fn context_switch(current: *mut ArchContext, next: *const ArchContext);
}
}

The context_switch function stores and restores CPU registers, and the ArchContext structure is an architecture-specific context structure.

Disable Preemption

In awkernel_async_lib/Cargo.toml, there is the no_preempt feature to disable preemption.

[features]
default = []
std = ["awkernel_lib/std", "no_preempt"]
no_preempt = []

If you want to disable preemption, please enable the no_preempt feature as default = ["no_preempt"].

Implementation

x86_64

For x86_64, the Context structure is defined in awkernel_lib/src/context/x86_64.rs as follows.

#![allow(unused)]
fn main() {
#[derive(Debug, Copy, Clone, Default)]
#[repr(C)]
pub struct Context {
    pub rbx: u64,
    pub rsp: u64,
    pub rbp: u64,
    pub r12: u64,
    pub r13: u64,
    pub r14: u64,
    pub r15: u64,
}
}

The Context structure is imported as the ArchContext in awkernel_lib/src/context.rs.

The context_switch function is implemented in assembly as follows. This stores and restores only general-purpose registers because the floating-point registers are stored and restored by each interrupt handler.

#![allow(unused)]
fn main() {
core::arch::global_asm!(
    "
.global context_switch
context_switch:
// Store general purpose registers
mov   [rdi], rbx
mov  8[rdi], rsp
mov 16[rdi], rbp
mov 24[rdi], r12
mov 32[rdi], r13
mov 40[rdi], r14
mov 48[rdi], r15

// Load general purpose registers
mov rbx,   [rsi]
mov rsp,  8[rsi]
mov rbp, 16[rsi]
mov r12, 24[rsi]
mov r13, 32[rsi]
mov r14, 40[rsi]
mov r15, 48[rsi]

ret
"
);
}

The Context:awkernel_lib/src/context.rs trait is implemented for the Context:awkernel_lib/src/context/x86_64.rs structure as follows. These methods set the stack pointer, entry point, and argument of the context.

#![allow(unused)]
fn main() {
impl crate::context::Context for Context {
    unsafe fn set_stack_pointer(&mut self, sp: usize) {
        self.rsp = sp as u64;
    }

    unsafe fn set_entry_point(&mut self, entry: extern "C" fn(usize) -> !, arg: usize) {
        self.r12 = arg as u64;
        self.r13 = entry as usize as u64;

        let entry_point_addr = entry_point as usize as u64;

        unsafe {
            core::arch::asm!("mov {}, rsp", lateout(reg) self.r15);
            core::arch::asm!("mov rsp, {}", in(reg) self.rsp);
            core::arch::asm!("push {}", in(reg) entry_point_addr);
            core::arch::asm!("mov rsp, {}", in(reg) self.r15);
        }

        self.rsp -= 8;
    }

    unsafe fn set_argument(&mut self, arg: usize) {
        self.r12 = arg as u64;
    }
}

extern "C" {
    fn entry_point() -> !;
}

global_asm!(
    "
.global entry_point
entry_point:
    mov  rdi, r12
    call r13
1:
    hlt
    jmp  1b
"
);
}

AArch64

For AArch64, the Context structure is defined in awkernel_lib/src/context/aarch64.rs as follows.

#![allow(unused)]
fn main() {
#[derive(Debug, Copy, Clone, Default)]
#[repr(C)]
pub struct Context {
    // 8 * 8 bytes
    pub fp_regs: FPRegs, // floating point registers

    //------------------------------ offset: 16 * 4 (+4)

    // 8 * 12 bytes
    pub gp_regs: GPRegs, // general purpose registers

    // ----------------------------- offset: 16 * 10 (+6)

    // 8 * 2 bytes
    pub sp: u64, // stack pointer
    _unused: [u8; 8],
}
}

The Context structure is imported as the ArchContext in awkernel_lib/src/context.rs.

The context_switch function is implemented in assembly as follows.

#![allow(unused)]
fn main() {
core::arch::global_asm!(
    "
.global context_switch
context_switch:
// Save the current context.

// Store floating-point registers.
stp      d8,  d9, [x0], #16
stp     d10, d11, [x0], #16
stp     d12, d13, [x0], #16
stp     d14, d15, [x0], #16

// Store general purpose registers.
stp     x19, x20, [x0], #16
stp     x21, x22, [x0], #16
stp     x23, x24, [x0], #16
stp     x25, x26, [x0], #16
stp     x27, x28, [x0], #16
stp     x29, x30, [x0], #16

// Store SP.
mov     x9, sp
str     x9, [x0]


// Restore the next context.

// Load floating-point registers.
ldp      d8,  d9, [x1], #16
ldp     d10, d11, [x1], #16
ldp     d12, d13, [x1], #16
ldp     d14, d15, [x1], #16

// Load general purpose registers.
ldp     x19, x20, [x1], #16
ldp     x21, x22, [x1], #16
ldp     x23, x24, [x1], #16
ldp     x25, x26, [x1], #16
ldp     x27, x28, [x1], #16
ldp     x29, x30, [x1], #16

// Load SP.
ldr     x9, [x1]
mov     sp, x9

ret
"
);
}

The Context:awkernel_lib/src/context.rs trait is implemented for the Context:awkernel_lib/src/context/aarch64.rs structure as follows. These methods set the stack pointer, entry point, and argument of the context.

#![allow(unused)]
fn main() {
impl crate::context::Context for Context {
    unsafe fn set_stack_pointer(&mut self, sp: usize) {
        self.sp = sp as u64;
    }

    unsafe fn set_entry_point(&mut self, entry: extern "C" fn(usize) -> !, arg: usize) {
        self.gp_regs.x19 = arg as u64;
        self.gp_regs.x20 = entry as usize as u64;
        self.gp_regs.x30 = entry_point as *const () as u64;
    }

    unsafe fn set_argument(&mut self, arg: usize) {
        self.gp_regs.x19 = arg as u64;
    }
}

extern "C" {
    fn entry_point() -> !;
}

global_asm!(
    "
.global entry_point
entry_point:
    mov     x0, x19
    blr     x20
1:
    wfi
    b       1b
"
);
}