Skip to content

Latest commit

 

History

History
257 lines (215 loc) · 6.22 KB

10-stdlib.org

File metadata and controls

257 lines (215 loc) · 6.22 KB

Standard library

The user code is now getting large enough that it would be useful to split it into a library. That will also make creating new user programs easier.

This is not going to be the full Rust standard library (far from it!) but we’ll try to have the same API as std where possible.

Adding a library

In the workspace root directory edit the Cargo.toml file:

  [workspace]
members = [
    "kernel",
    "hello",
    "euralios_std"  # New
]

then run cargo to create the euralios_std crate:

cargo new euralios_std --lib

Our user program hello is going to depend on this library, so in hello/Cargo.toml:

[dependencies]
linked_list_allocator = "0.9.0"
euralios_std = { path = "../euralios_std" }  # New

Moving syscalls into library

In the library we can now start adding some functions. In euralios_std/src/lib.rs:

#![no_std]
#![no_main]

pub mod syscalls;

and in a new file euralios_std/src/syscalls.rs we can start by moving the thread_exit(), receive() and send() functions out of hello/src/main.rs:

use core::arch::asm;

pub fn thread_exit() -> ! {
    unsafe {
        asm!("mov rax, 1", // exit_current_thread
             "syscall",
             out("rcx") _,  // syscall clobber
             out("r11") _); // syscall clobber
    }
    loop{}
}

Note that the rcx and r11 registers are now marked as clobbered, because the syscall instruction modifies these registers. The code worked before because fortunately the rust compiler/LLVM backend didn’t use those registers. Without these statements I ran into strange page faults when more data than could fit in one register was returned from a function containing a syscall instruction, caused by overwriting these registers without telling the compiler.

pub fn receive(handle: u64) -> Result<u64, u64> {
    let mut err: u64;
    let value: u64;
    unsafe {
        asm!("mov rax, 3", // sys_receive
             "syscall",
             in(rdi) handle,
             lateout("rax") err,
             lateout("rdi") value,
             out("rcx") _,
             out("r11") _);
    }
    if err == 0 {
        return Ok(value);
    }
    Err(err)
}

pub fn send(handle: u64, value: u64) -> Result<(), u64> {
    let err: u64;
    unsafe {
        asm!("mov rax, 4", // sys_send
             "syscall",
             in("rdi") handle,
             in("rsi") value,
             lateout("rax") err,
             out("rcx") _,
             out("r11") _);
    }
    if err == 0 {
        return Ok(());
    }
    Err(err)
}

so the hello program _start() function can become:

  use euralios_std::syscalls; // New

  #[no_mangle]
pub unsafe extern "sysv64" fn _start() -> ! {
    // Information passed from the operating system
    let heap_start: usize;
    let heap_size: usize;
    asm!("",
         lateout("rax") heap_start,
         lateout("rcx") heap_size,
         options(pure, nomem, nostack)
    );
    println!("Heap start {:#016X}, size: {} bytes ({} Mb)", heap_start, heap_size, heap_size / (1024 * 1024));

    ALLOCATOR.lock().init(heap_start, heap_size);

    loop{
        let value = syscalls::receive(0).unwrap();
        let ch = char::from_u32(value as u32).unwrap();
        println!("Received: {} => {}", value, ch);
        if ch == 'x' {
            println!("Exiting");
            break;
        }
        syscalls::send(1, value).unwrap();
    }

    syscalls::thread_exit();
}

Debug output

use core::arch::asm;
use core::format_args;
use core::fmt;

struct Writer {}

impl fmt::Write for Writer {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        unsafe {
            asm!("mov rax, 2", // syscall function
                 "syscall",
                 in("rdi") s.as_ptr(), // First argument
                 in("rsi") s.len(), // Second argument
                 out("rcx") _,
                 out("r11") _);
        }
        Ok(())
    }
}

pub fn _print(args: fmt::Arguments) {
    use core::fmt::Write;
    Writer{}.write_fmt(args).unwrap();
}

#[macro_export]
macro_rules! debug_print {
    ($($arg:tt)*) => ($crate::debug::_print(format_args!($($arg)*)));
}

#[macro_export]
macro_rules! debug_println {
    () => ($crate::debug_print!("\n"));
    ($($arg:tt)*) => ($crate::debug_print!("{}\n", format_args!($($arg)*)));
}

Memory

Remove linked_list_allocator dependency from hello/Cargo.toml, and add to euralios_std/Cargo.toml

In memory.rs

extern crate alloc;
use linked_list_allocator::LockedHeap;

use crate::debug_println;

#[global_allocator]
static ALLOCATOR: LockedHeap = LockedHeap::empty();

pub fn init(heap_start: usize, heap_size: usize) {
    debug_println!("Heap start {:#016X}, size: {} bytes ({} Mb)", heap_start, heap_size, heap_size / (1024 * 1024));
    unsafe {ALLOCATOR.lock().init(heap_start, heap_size);}
}

// Allocator error handler
#[alloc_error_handler]
fn alloc_error_handler(layout: alloc::alloc::Layout) -> ! {
    panic!("allocation error: {:?}", layout)
}

User entry point

In lib.rs:

// User program entry point
extern {
    fn main() -> ();
}

#[no_mangle]
pub unsafe extern "sysv64" fn _start() -> ! {
    // Information passed from the operating system
    let heap_start: usize;
    let heap_size: usize;
    asm!("",
         lateout("rax") heap_start,
         lateout("rcx") heap_size,
         options(pure, nomem, nostack)
    );
    memory::init(heap_start, heap_size);

    main(); // New

    syscalls::thread_exit();
}

Final user program

The hello program now consists of:

#![no_std]
#![no_main]

use euralios_std::{debug_println, syscalls};

#[no_mangle]
fn main() {
    loop{
        let value = syscalls::receive(0).unwrap();
        let ch = char::from_u32(value as u32).unwrap();
        debug_println!("Received: {} => {}", value, ch);
        if ch == 'x' {
            debug_println!("Exiting");
            break;
        }
        syscalls::send(1, value).unwrap();
    }
}