Writing a Toy OS in Rust
A Step-by-Step Guide to Building Your Own Minimal Operating System
Meta Description:
Learn how to build a minimal operating system in Rust—from bootloader to syscalls. Perfect for developers exploring low-level programming, OS internals, and Rust’s no_std environment.
Table of Contents
- Why Build an OS in Rust?
- Prerequisites
- Setting Up the Bootloader
- Entering 32-bit Protected Mode
- Memory Management: Paging & Heap Allocation
- Handling Hardware Interrupts
- Implementing System Calls (Syscalls)
- Running Your OS: QEMU & Hardware
- Further Reading & Resources
1. Why Build an OS in Rust? <a name=”why-rust”></a>
Rust’s memory safety guarantees make it ideal for OS development:
✅ No segfaults (thanks to the borrow checker)
✅ Fearless concurrency (no data races in kernel code)
✅ Zero-cost abstractions (performance like C, safety like Java)
Use Cases:
- Embedded systems
- Hypervisors
- Custom real-time kernels
2. Prerequisites <a name=”prerequisites”></a>
Tools Needed
- Rust (
rustup target add thumbv7em-none-eabihf) - QEMU (for emulation)
- GRUB (for multiboot compliance)
- Xorriso (for ISO generation)
Key Crates
rust
# Cargo.toml [dependencies] bootloader = "0.10" x86_64 = "0.14" spin = "0.9" // For synchronization volatile = "0.4" // Safe MMIO access
3. Setting Up the Bootloader <a name=”bootloader”></a>
Minimal Boot Code (x86 Assembly)
nasm
; boot.asm
section .multiboot_header
header_start:
dd 0xE85250D6 ; Magic number
dd 0 ; Architecture (i386)
dd header_end - header_start ; Header length
; Checksum
dd 0x100000000 - (0xE85250D6 + 0 + (header_end - header_start))
header_end:
Rust Entry Point
rust
// main.rs
#![no_std]
#![no_main]
#[no_mangle]
pub extern "C" fn _start() -> ! {
loop {} // Hang (we’ll add functionality soon)
}
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
}
4. Entering 32-bit Protected Mode <a name=”protected-mode”></a>
Global Descriptor Table (GDT)
rust
use x86_64::structures::gdt::{GlobalDescriptorTable, Descriptor};
let mut gdt = GlobalDescriptorTable::new();
gdt.add_entry(Descriptor::kernel_code_segment());
gdt.load();
Enabling Paging
rust
use x86_64::structures::paging::{PageTable, Page};
let l4_table = unsafe { &mut *(0xffffffff_fffff000 as *mut PageTable) };
l4_table[511].set_addr(phys_addr, PageTableFlags::PRESENT | PageTableFlags::WRITABLE);
5. Memory Management <a name=”memory-management”></a>
Heap Allocation with alloc
rust
#![feature(alloc_error_handler)] extern crate alloc; use alloc::boxed::Box; let heap_value = Box::new(42);
Buddy Allocator Implementation
rust
struct BuddyAllocator {
// Implementation details...
}
unsafe impl GlobalAlloc for BuddyAllocator {
// Required methods
}
6. Handling Hardware Interrupts <a name=”interrupts”></a>
Programmable Interrupt Controller (PIC) Setup
rust
use x86_64::instructions::port::Port;
let mut pic1_cmd = Port::new(0x20);
let mut pic1_data = Port::new(0x21);
unsafe {
pic1_cmd.write(0x11); // ICW1: Initialize
pic1_data.write(0x20); // ICW2: IRQ0-7 -> INT 0x20-0x27
}
Keyboard Interrupt Handler
rust
extern "x86-interrupt" fn keyboard_handler(_stack_frame: InterruptStackFrame) {
let scancode = unsafe { Port::new(0x60).read() };
print!("{}", scancode);
}
7. Implementing System Calls <a name=”syscalls”></a>
Syscall Table (x86-64)
rust
pub const SYS_WRITE: u64 = 1;
pub const SYS_EXIT: u64 = 60;
pub unsafe fn syscall(num: u64, arg1: u64, arg2: u64, arg3: u64) -> u64 {
let ret: u64;
asm!(
"syscall",
in("rax") num, in("rdi") arg1, in("rsi") arg2, in("rdx") arg3,
lateout("rax") ret,
);
ret
}
Example: write() Syscall
rust
pub fn sys_write(fd: u32, buf: &[u8]) -> Result<usize, SyscallError> {
if fd != 1 { return Err(SyscallError::InvalidFD); }
// Print to VGA buffer or serial port
Ok(buf.len())
}
8. Running Your OS <a name=”running-the-os”></a>
QEMU Command
bash
qemu-system-x86_64 -drive format=raw,file=target/os.iso
Testing on Real Hardware
- Flash to USB:bashdd if=target/os.iso of=/dev/sdX bs=4M status=progress
- Boot via BIOS/UEFI.
9. Further Reading <a name=”resources”></a>
📚 Books
- “Writing an OS in Rust” by Philipp Oppermann
- “Operating Systems: Three Easy Pieces”
🔗 Projects to Study
- Redox OS (Full-featured Rust OS)
- Theseus OS (Stateful Rust kernel)
Final Thoughts
Building an OS in Rust teaches:
- Hardware abstraction
- Concurrency without data races
- How syscalls really work
Challenge: Try adding a filesystem or multitasking next!
Filed under: Kernel Dives,Web Development - @ July 21, 2025 12:07 pm
Hi, this is a comment.
To get started with moderating, editing, and deleting comments, please visit the Comments screen in the dashboard.
Commenter avatars come from Gravatar.