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.
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
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();
}
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)*)));
}
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)
}
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();
}
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();
}
}