In this short guide I will go over adding Rust “modules” to a base xv6 kernel for the x86 target (although it could be adapted to RISC-V with minimal changes).
While xv6 doesn’t really support “modules” in the same way that the Linux kernel does, here I describe a module simply as a subsystem of the kernel that in this case will be completely done in Rust.
With this approach you gain memory safety in all the code you abstract to Rust, and only stay unsafe in:
Gaining:
This approach uses:
rustmod/)librustmod.a1)Inside of the root xv6 project, create a rustmod crate:
mkdir rustmod
cd rustmod
cargo init --lib
And make this your Cargo.toml:
[package]
name = "rustmod"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["staticlib"]
[profile.release]
panic = "abort"
lto = true
codegen-units = 1
And make this your .cargo/config.toml
[build]
target = "i686-unknown-linux-gnu"
[target.i686-unknown-linux-gnu]
linker = "gcc"
rustflags = [
"-C", "relocation-model=static",
"-C", "link-arg=-m32",
"-C", "target-feature=-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-avx,-avx2",
"-C", "soft-float",
]
This will make cargo to emit a .a file suitable for linking with the xv6 kernel.
Let’s write a basic Rust function to test that everything is working correctly:
rustmod/src/lib.rs
#![no_std]
use core::panic::PanicInfo;
extern "C" {
fn c_kputs(msg: *const u8);
}
#[no_mangle]
pub extern "C" fn rust_test() {
static MSG: &[u8] = b"Rust support loaded\n\0";
unsafe {
c_kputs(MSG.as_ptr());
}
}
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
#![no_std] is required because the kernel provides no standard library.#[no_mangle] and extern "C" ensure ABI compatibility with xv6.panic = "abort" avoids Rust’s complex unwinding.Inside kernel/include/defs.h add:
// external rust
extern void rust_test(void);
And in console.c add:
void
c_kputs(const char *s)
{
cprintf("%s", s);
}
Finally, in main() add the rust_test(); function:
int
main(void)
{
...
userinit();
rust_test();
mpmain();
}
The xv6 kernel is built using kernel/CMakeLists.txt. This file has to be modified such that it:
Add this to kernel/CMakeLists.txt
# Path to the rust staticlib
set(RUSTMOD_DIR ${CMAKE_SOURCE_DIR}/rustmod)
set(RUSTMOD_TARGET i686-unknown-linux-gnu)
set(RUSTMOD_LIB ${RUSTMOD_DIR}/target/${RUSTMOD_TARGET}/release/librustmod.a)
# Build the Rust kernel module with cargo
add_custom_target(rustmod_build
COMMAND cargo build --release
WORKING_DIRECTORY ${RUSTMOD_DIR}
BYPRODUCTS ${RUSTMOD_LIB}
COMMENT "Building Rust kernel module"
)
Which defines:
cargo build --release --target i686-unknown-linux-gnu
To output librustmod.a.
Find wherever you’re linking the kernel and all kernel objects:
COMMAND ld -m elf_i386 -nostdlib -T kernel.ld ...
${kernel_OBJECTS} -b binary initcode entryother
And change it with:
COMMAND ld -m elf_i386 -nostdlib -T ${CMAKE_CURRENT_SOURCE_DIR}/kernel.ld -o kernel
${kernel_OBJECTS} ${RUSTMOD_LIB} -b binary initcode entryother
DEPENDS
...
rustmod_build
Finally, when building and starting xv6, you should see:
SeaBIOS (version 1.15.0-1)
iPXE (https://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+1FF8B4A0+1FECB4A0 CA00
Booting from Hard Disk..xv6...
Rust support loaded. <-- From Rust!
cpu0: starting 0
sb: size 20000 nblocks 19937 ninodes 200 nlog 30 logstart 2 inodestart 32 bmap start 58
init: starting sh
$ ls
. 1 1 512
.. 1 1 512
init 2 2 46876
README 2 3 2170
sh 2 4 54604
ls 2 5 48684
cat 2 6 47016
rm 2 7 46448
test_sha256 2 8 48236
test_aes256 2 9 47872
console 3 10 0
$