Created by Intelligentia Artificialis, guided by Homo Sapiens!
Rix is a minimalist 64-bit operating system written from scratch in Rust and x86-64 assembly. It boots directly without GRUB or any other bootloader, switching from real mode to 64-bit long mode entirely on its own. This is a memory-safe OS, leveraging Rust's ownership system to prevent entire classes of bugs.
- Self-contained bootloader: No GRUB or external bootloader needed
- 64-bit long mode: Full x86_64 operation
- Memory-safe kernel: Rust's ownership prevents use-after-free, double-free, and buffer overflows
- Type-safe by design: Strong typing eliminates many classes of bugs at compile time
- VGA text mode: 80x25 color terminal
- PS/2 keyboard support: Full keyboard input
- In-memory filesystem: Create, edit, and manage files
- Interactive shell: Unix-like command interface
- App loader: Run precompiled apps bundled in the OS image
- User mode execution: Ring 3 applications with memory protection
- Preemptive multitasking: Timer-based process scheduling with background process support
- Per-process page tables: Each user process gets isolated virtual address space
- Process management: PCB-based process tracking with parent-child relationships
- System calls: Clean syscall interface via INT 0x80
- Exception handling: Graceful handling of CPU exceptions
Rix includes two shells:
| Shell | Mode | File | Description |
|---|---|---|---|
Kernel Shell (kshell) |
Ring 0 | kernel/kshell.rs |
Built into the kernel, runs with full privileges |
User Shell (shell) |
Ring 3 | apps/shell/shell.rs |
Runs as a user-mode application with memory protection |
Default behavior: The OS launches the user shell (shell) by default. If the user shell crashes or exits, the kernel automatically restarts it. If the user shell is not available, the kernel falls back to the kernel shell.
To use the kernel shell as the default instead of the user shell, modify kernel/kernel.rs:
// Change this in kernel_main():
loop {
if loader::app_exists("shell") {
// Launch user shell
let result = loader::app_execute("shell", 0, core::ptr::null());
// ... restart logic ...
} else {
// Fall back to kernel shell
kshell::run();
}
}
// To always use kernel shell, replace the loop with:
loop {
kshell::run();
}Rix supports two types of applications:
| Type | Execution Mode | Output Method | Example |
|---|---|---|---|
| Kernel Apps | Ring 0 (kernel mode) | Function pointer from kernel | kcalc |
| User Apps | Ring 3 (user mode) | System calls (INT 0x80) | calc, shell, hello |
Kernel apps run with full system privileges and can directly access hardware. They receive a function pointer to the kernel's print function.
User apps run in protected mode with memory isolation. They communicate with the kernel through system calls. If a user app crashes, the kernel catches the exception and terminates it gracefully without affecting system stability.
Each user application runs as a separate process with:
| Component | Description |
|---|---|
| PID | Unique process identifier (1-15) |
| PCB | Process Control Block with state, context, and parent PID |
| Page Tables | Separate PML4, PDPT, PD, and user PT per process |
| CR3 | Dedicated page table root address |
| Physical Pages | Isolated physical memory for code, data, and stack |
Process lifecycle:
create_process()- Allocates PID, creates page tables, maps user memorysetup_process_context()- Initializes registers, entry point, stack pointer- Process executes in Ring 3 with its own CR3
exit_process()- Marks process as zombie, prepares for cleanupdestroy_process()- Frees all physical pages and page tables
Memory isolation: Each process has its own virtual-to-physical mappings for the user space region (0x400000-0x500000). Kernel memory is shared but supervisor-only, preventing user code from accessing kernel data.
| Command | Description |
|---|---|
help |
Display available commands |
clear |
Clear the screen |
apps |
List available apps |
ps |
List running processes |
kill <pid> |
Terminate a process by PID |
ls [path] |
List directory contents |
cat <file> |
Display file contents |
touch <file> |
Create empty file |
mkdir <dir> |
Create directory |
rm <path> |
Remove file or empty directory |
cd <dir> |
Change directory |
pwd |
Print working directory |
echo <text> |
Print text |
reboot |
Reboot the system (kernel shell only) |
You can run any app in the background by appending & to the command:
tick & # Run tick in background
tock & # Run tock in background
ps # Show running processes
kill 2 # Kill process with PID 2Kernel-mode calculator that runs with Ring 0 privileges. Uses direct function calls for output.
Usage:
kcalc <expression>
Operators: + - * x / % ()
Examples:
kcalc 10+5 # Result: 15
kcalc "42 * 3" # Result: 126
kcalc 2(3+4) # Result: 14 (implicit multiplication)User-mode calculator that runs with Ring 3 protection. Uses system calls for output. Same functionality as kcalc but demonstrates user-mode execution.
Usage:
calc <expression>
Examples:
calc 10+5 # Result = 15
calc "100 / 4" # Result = 25
calc 2(3+4) # Result = 14Testing tool for kernel exception handlers. Verifies that CPU exceptions from user mode are caught gracefully without crashing the kernel. Defaults to divide-by-zero test (crash type 0).
Usage:
crashThe program runs automatically and triggers a divide-by-zero exception (#DE). The kernel catches it, terminates the user program, and returns to the shell.
Available Crash Types:
To test other crash types, modify the source and recompile:
0- Divide by zero (#DE) - default1- Invalid opcode (#UD)2- General protection fault (#GP)3- Page fault - read (#PF)4- Page fault - write (#PF)5- Breakpoint (#BP)6- Overflow (#OF)
Example Session:
> crash
CRASH - User Mode Crash Tester
================================
Triggering crash type 0: Divide by zero (#DE)
[USERMODE] Exited with code: 0
> _
The program tests that:
- Exceptions are caught and handled correctly
- User program terminates gracefully
- Kernel and shell remain stable
- Serial log shows exception markers (
!0,!GP,!PF, etc.)
Minimal user mode program that prints a greeting and exits. Demonstrates basic user mode execution and syscalls.
Usage:
helloPrints "tick" every 2 seconds for 10 seconds. Demonstrates background process execution and preemptive multitasking.
Usage:
tick & # Run in backgroundPrints "tock" every 2 seconds for 10 seconds. Run alongside tick to see multitasking in action.
Usage:
tock & # Run in backgroundExample multitasking session:
tick & # Start tick in background
tock & # Start tock in background
ps # See both processes running
# Watch as tick and tock alternate printing
# After 10 seconds, both will finish automaticallymacOS:
nasm- Netwide Assemblerrustc(nightly) - Rust compilercargo- Rust package managerllvm- For linker and objcopyqemu-system-x86_64- For testing (optional)
Linux:
nasm- Netwide Assemblerrustc(nightly) - Rust compilercargo- Rust package managerlld- LLVM Linkermake- GNU Makeqemu-system-x86_64- For testing (optional)
The easiest way to get started is using the provided setup script:
# This will install Rust nightly and required components
./setup.sh
# Then build and run
make
make runThe setup script handles Rust installation and configuration automatically. This is particularly useful for the Rust version as it requires specific nightly toolchain setup.
If you prefer to set up dependencies manually:
macOS:
# Install Rust (nightly)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup default nightly
rustup component add rust-src llvm-tools
# Install dependencies via Homebrew
brew install nasm qemu llvm
# Build
makeLinux:
# Install Rust (nightly)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup default nightly
rustup component add rust-src llvm-tools
# Install dependencies (Debian/Ubuntu)
sudo apt install nasm lld make qemu-system-x86
# Build
makeThe build creates:
build/rix.img- Bootable OS image with bundled appsbuild/kernel.bin- Kernel binarybuild/apps.img- App table with all apps
make runOr manually:
qemu-system-x86_64 -drive format=raw,file=build/rix.img -m 256Mqemu-system-x86_64 -drive format=raw,file=build/rix.img -m 256M -serial stdioIf the system reboots or you see an Invalid Opcode exception (!6 on serial) when launching an app (e.g., calc), ensure the kernel enables SSE/SSE2 early. Modern x86_64 compilers emit SSE instructions by default. The kernel must:
- Clear CR0.EM (bit 2)
- Set CR0.MP (bit 1)
- Set CR4.OSFXSR (bit 9)
- Set CR4.OSXMMEXCPT (bit 10)
This project configures these in kernel/entry.asm before calling into Rust. Without this, executing SSE instructions will raise #UD.
Write the image to a USB drive (
sudo dd if=build/rix.img of=/dev/sdX bs=512raios/
├── boot/
│ ├── stage1.asm # Stage 1 bootloader (MBR, loads kernel and apps)
│ └── stage2.asm # Stage 2 bootloader (mode switching to 64-bit)
├── kernel/
│ ├── entry.asm # Kernel entry point (assembly)
│ ├── kernel.rs # Main kernel code
│ ├── vga.rs # VGA text mode driver
│ ├── keyboard.rs # PS/2 keyboard driver
│ ├── fs.rs # In-memory filesystem
│ ├── shell.rs # Command shell
│ ├── loader.rs # App loader
│ ├── syscall.rs # System call handler
│ ├── usermode.rs # User mode support
│ ├── paging.rs # Page table management and physical memory allocator
│ ├── process.rs # Process Control Block and process management
│ ├── string.rs # String utilities
│ ├── io.rs # Port I/O functions
│ ├── linker.ld # Linker script
│ ├── build.rs # Build script
│ └── Cargo.toml # Kernel dependencies
├── apps/
│ ├── userlib.rs # User mode library
│ ├── app.ld # App linker script
│ ├── calc/ # Calculator app
│ │ ├── Cargo.toml
│ │ └── calc.rs
│ ├── hello/ # Simple user mode demo
│ │ ├── Cargo.toml
│ │ └── hello.rs
│ └── ucrash/ # Exception handler tester
│ ├── Cargo.toml
│ └── ucrash.rs
├── build_apptable.py # Script to create app table
├── setup.sh # First-time setup script
├── Makefile # Build system
├── Cargo.toml # Workspace configuration
└── README.md # This file
- BIOS loads Stage 1 bootloader from MBR to 0x7C00
- Stage 1 bootloader:
- Sets up stack and segments
- Loads Stage 2 bootloader (sectors 2-5)
- Loads kernel from disk to memory below 1MB (sectors 6+)
- Loads app table (right after kernel, LBA sector 69) to 0x20000 using extended BIOS reads
- Jumps to Stage 2
- Stage 2 bootloader:
- Checks for 64-bit CPU support
- Enables A20 line
- Enters 32-bit protected mode
- Copies kernel to 1MB mark (0x100000)
- Copies app table to 2MB mark (0x200000)
- Sets up identity-mapped page tables
- Enables PAE and long mode
- Enters 64-bit long mode
- Jumps to kernel
- Kernel:
- Initializes VGA, keyboard, filesystem
- Loads and verifies app table
- Starts interactive shell
When running with serial output (-serial stdio), the bootloader outputs single-character markers to indicate progress. This is useful for debugging boot issues.
| Marker | Stage | Meaning |
|---|---|---|
1 |
stage1.asm | Stage 1 started |
2 |
stage1.asm | Stage 2 loaded |
3 |
stage2.asm | Stage 2 started |
4 |
stage2.asm | App table loaded |
X |
stage2.asm | App table load FAILED |
5 |
stage2.asm | Long mode check passed |
P |
stage2.asm | Protected mode |
6 |
stage2.asm | Kernel loaded via ATA PIO |
7 |
stage2.asm | App table copied to final address (5MB) |
A |
stage2.asm | Page tables cleared |
B |
stage2.asm | Page table setup done |
C |
stage2.asm | Jump to 64-bit long mode |
D |
stage2.asm | Entered long mode |
8 |
entry.asm | Entered kernel |
Normal boot sequence: 12345P67ABCD8
If you see X instead of 4, the app table failed to load from disk.
| Address | Usage |
|---|---|
| 0x00000 - 0x07BFF | Free (BIOS data) |
| 0x07C00 - 0x07DFF | Stage 1 bootloader (512 bytes) |
| 0x07E00 - 0x0FDFF | Stage 2 bootloader (2KB) |
| 0x10000 - 0x1FFFF | Kernel temp load area |
| 0x20000 - 0x2FFFF | App table temp load area |
| 0x70000 - 0x73FFF | Boot page tables (16KB) |
| 0x90000 | Stack |
| 0xB8000 | VGA text buffer |
| 0x100000 (1MB) | Kernel code and data |
| 0x400000 (4MB) | User virtual space (per-process mapped) |
| 0x500000 (5MB) | App table and app binaries |
| 0x1000000 (16MB)+ | Physical pages for process page tables and user memory |
Each user process has its own page tables providing isolated virtual address space:
| Virtual Address | Physical Mapping | Access |
|---|---|---|
| 0x000000 - 0x200000 | Identity mapped | Supervisor only (kernel code) |
| 0x200000 - 0x400000 | Identity mapped | Supervisor only (kernel data) |
| 0x400000 - 0x4F0000 | Per-process physical pages | User (code, data, stack) |
| 0x4F0000 - 0x500000 | Per-process physical pages | User (stack region) |
| 0x500000 - 0x1800000 | Identity mapped (2MB pages) | Supervisor only (app table, kernel data) |
Rix uses x86-64 4-level paging with per-process page tables:
PML4 (Page Map Level 4)
└── PDPT (Page Directory Pointer Table)
└── PD (Page Directory)
├── [0-1] → Kernel PT (4KB pages, supervisor only)
├── [2] → User PT (4KB pages, per-process)
└── [3-11] → 2MB pages (kernel data, supervisor only)
Key features:
- Kernel pages shared: PD entries 0-1 point to kernel page tables (supervisor only)
- User pages isolated: PD entry 2 points to per-process page table
- Physical allocator: Bitmap-based, allocates from 16MB onwards
- CR3 switching: Each process switch updates CR3 to the process's PML4
The app table is stored right after the kernel (LBA sector 69) and contains:
+------------------+
| Table Header | (16 bytes)
| - Magic: APP_ |
| - Version: 1 |
| - App count |
| - Reserved |
+------------------+
| App Header 1 | (48 bytes each)
| - Name (32) |
| - Offset |
| - Size |
| - Entry offset |
+------------------+
| App Header 2 |
+------------------+
| ... |
+------------------+
| App Binary 1 | (variable size)
+------------------+
| App Binary 2 |
+------------------+
| ... |
+------------------+
- Stage 1 bootloader loads app table from disk using INT 13h extended read (LBA mode)
- Stage 2 bootloader copies app table from 0x20000 to 0x200000 in protected mode
- Kernel initializes loader and verifies magic number
- Shell can execute apps by name
- User types app name and arguments
- Shell checks if command is built-in, then checks if it's an app
- Loader finds app in table by name
- Loader calls app entry point with argc/argv
- App executes and returns output buffer offset
- Loader displays app output via VGA
- Control returns to shell
- Minimal assembly: Only bootloader and entry point are in assembly
- Maximum Rust code: All drivers, shell, and loader implemented in Rust
- Memory safety: Rust's ownership prevents entire classes of bugs
- No external dependencies: No std library, no GRUB, completely freestanding
- Extensible: Apps can be added without modifying the kernel
- Educational: Clean, readable code demonstrating safe systems programming
Create a new directory in apps/ with a Cargo.toml and source file:
mkdir apps/myappCreate apps/myapp/Cargo.toml:
[package]
name = "myapp"
version = "1.0.0"
edition = "2021"
[lib]
crate-type = ["staticlib"]Create apps/myapp/src/lib.rs:
#![no_std]
#![no_main]
use core::panic::PanicInfo;
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
// Syscall constants
const SYS_WRITE: u64 = 1;
const SYS_EXIT: u64 = 0;
// Helper function to print strings
fn print(s: &str) {
unsafe {
core::arch::asm!(
"int 0x80",
in("rax") SYS_WRITE,
in("rdi") 1u64,
in("rsi") s.as_ptr() as u64,
in("rdx") s.len() as u64,
);
}
}
// App entry point
#[no_mangle]
pub extern "C" fn _start() -> i32 {
print("Hello from myapp!\n");
// Your app logic here
// Exit syscall
unsafe {
core::arch::asm!(
"int 0x80",
in("rax") SYS_EXIT,
in("rdi") 0u64,
);
}
0
}- Add your app directory to
apps/ - Update workspace
Cargo.tomlto include the new app - Update Makefile:
- Add to
APP_BINS - Add build rules (see calc example)
- Add to
- Run
maketo build - Run
make runto test
- Apps run in user mode (Ring 3) with memory protection
- No heap allocator yet (stack-based allocation only)
- Communication via syscalls (SYS_WRITE, SYS_EXIT)
- Output via syscall interface
- No standard library (must use no_std)
- Position-independent code recommended
- Ownership system: Prevents use-after-free and double-free
- Bounds checking: Array bounds checking prevents buffer overflows
- Option: Makes null handling explicit
- Strong typing: Enums and pattern matching catch logic errors
- Concurrency safety: Compiler prevents data races
Rust still requires unsafe for:
- Hardware Access: Port I/O, memory-mapped I/O
- Assembly Integration: Calling assembly functions, inline asm
- Global Mutable State: Static variables (can use atomics to make safer)
However, these unsafe blocks are:
- Clearly marked with
unsafekeyword - Isolated in specific modules (io, vga, keyboard)
- Wrapped with safe APIs for the rest of the kernel
- Much smaller surface area than traditional C kernels (~7% of code)
- Heap allocator (enable alloc crate)
- More syscalls (read, open, close)
- Better error handling with Result<T, E>
- Standard app library
- Nested process execution (launching apps from shell)
- Dynamic memory allocation for apps
- App-to-app communication
- File I/O from apps
- Preemptive multitasking
- Network stack
- ✅ Per-process page tables with CR3 switching
- ✅ Process Control Block (PCB) management
- ✅ Physical memory allocator for user processes
- ✅ Memory isolation between processes | Code Size | ~3000 LOC | ~4000 LOC | | Unsafe Code | All code | ~7% isolated |
This is free and unencumbered software released into the public domain.
Rix is designed as an educational operating system with emphasis on clarity, safety, and maintainability. When contributing code or using AI tools to generate code, please adhere to these principles.
- Prefer Rust for all new code: Maximum amount of code should be written in Rust
- Assembly only when absolutely necessary: Use assembly language only for:
- Bootloader (real mode, protected mode, long mode transitions)
- Kernel entry point (
entry.asm) - CPU-specific operations that cannot be done in Rust (e.g., loading GDT, IDT, control registers)
- Performance-critical inline assembly within Rust functions
- Memory Safety: Rust prevents memory corruption bugs at compile time
- Type Safety: Strong type system catches bugs early
- Readability: Rust code is easier to understand and maintain than assembly
- Productivity: Faster development with modern language features
- Educational: Rust teaches safe systems programming practices
- Write clear, understandable code: Prefer clarity over cleverness
- Use descriptive names: Variables, functions, and types should have meaningful names
- Keep functions focused: Each function should do one thing well
- Comment complex logic: If code is not immediately obvious, add comments
- Avoid premature optimization: Correct first, optimize later if needed
Example:
// Good - Clear and simple
fn calculate_total(items: &[i32]) -> i32 {
items.iter().sum()
}
// Avoid - Unnecessarily clever
fn calculate_total(i: &[i32]) -> i32 {
i.iter().fold(0, |a, &b| a + b)
}- Minimize unsafe code: Only use
unsafewhen absolutely necessary - Wrap unsafe in safe APIs: Encapsulate unsafe code in safe abstractions
- Check all inputs: Validate function parameters
- Use Option and Result<T, E>: Make error handling explicit
- Bounds checking: Rust does this automatically with slices
Example:
// Good - Safe with validation
fn read_file(filename: &str, buffer: &mut [u8]) -> Result<usize, &'static str> {
if filename.is_empty() {
return Err("Empty filename");
}
if buffer.is_empty() {
return Err("Empty buffer");
}
// ... rest of implementation
Ok(0)
}- Handle errors explicitly: Use Result<T, E> for operations that can fail
- Avoid panics in production code: Panic only for unrecoverable errors
- Resource management: Rust's RAII automatically cleans up resources
- Defensive programming: Use assertions and type system to enforce invariants
- Leverage Rust's guarantees: Ownership, borrowing, and lifetimes prevent most memory bugs
- Minimize unsafe blocks: Keep them small, well-documented, and auditable
- Use safe abstractions: Wrap hardware access in safe APIs
- Never trust user input: Always validate input from keyboard, files, apps
- Sanitize strings: Use Rust's string handling to prevent buffer overflows
- Validate indices: Rust's bounds checking prevents out-of-bounds access
- Kernel/user separation: Maintain clear separation between Ring 0 and Ring 3
- Capability-based security: Limit what apps can do
- No unnecessary privileges: Apps should run with minimal rights
kernel/
├── kernel.rs # Main kernel entry
├── vga.rs # VGA driver
├── keyboard.rs # Keyboard driver
├── fs.rs # Filesystem
├── shell.rs # Command shell
├── loader.rs # App loader
└── ... # One feature per file
apps/
├── calc/ # Calculator app
│ ├── Cargo.toml
│ └── calc.rs
└── ... # One app per directory
- Document public functions: Use doc comments (
///) - Keep APIs minimal: Expose only what's necessary
- Use strong types: Create wrapper types instead of using primitives
- Make impossible states unrepresentable: Use the type system
Example:
/// Initialize the VGA text mode driver
///
/// Sets up 80x25 color text mode and clears the screen.
/// This must be called before any VGA operations.
pub fn init() {
// Implementation
}- Use rustfmt: Always format code with
cargo fmt - Follow Rust conventions: Use standard Rust naming and style
- Line length: Prefer 80-100 characters
- Functions:
snake_case - Types/Structs:
PascalCase - Constants:
SCREAMING_SNAKE_CASE - Modules:
snake_case
- Doc comments for public APIs: Use
///for documentation - Inline comments for complex logic: Explain why, not what
- TODO comments: Mark areas that need work with
// TODO:
- Test in QEMU: Always test in emulator first
- Test edge cases: Try boundary values
- Test error paths: Verify error handling works
- Test on real hardware: Eventually test on physical machines
- Self-review: Review your own code before committing
- Run cargo check: Verify code compiles
- Run cargo clippy: Fix all warnings
- Test thoroughly: Don't assume code works
When you must use unsafe:
- Document why it's necessary: Explain what safety invariants you're maintaining
- Keep it minimal: Make unsafe blocks as small as possible
- Encapsulate in safe APIs: Don't expose unsafe to callers
- Add safety comments: Document all safety requirements
Example:
/// Write a byte to a port
///
/// # Safety
///
/// The caller must ensure that writing to this port is safe
/// and will not cause undefined behavior or hardware issues.
pub unsafe fn outb(port: u16, value: u8) {
core::arch::asm!(
"out dx, al",
in("dx") port,
in("al") value,
);
}When using AI tools (like GitHub Copilot, ChatGPT, etc.) to generate code:
- Specify Rust preference: Always request Rust implementations over C/assembly
- Request safe code: Ask for code that minimizes
unsafe - Ask for error handling: Request Result<T, E> for fallible operations
- Request documentation: Ask for doc comments
- Review generated code: Never blindly accept AI-generated code
- Test thoroughly: AI can make mistakes - always test
- Run clippy: Check for common issues
IMPORTANT: AI tools should NOT create separate documentation files unless specifically requested.
Rules for documentation:
-
Use existing documentation files only:
README.md- Main project documentation, user-facing features, and contributing guidelines
-
Update README.md for new features:
- Add new apps to the appropriate section
- Add new shell commands to the commands list
- Update examples and usage instructions
- Keep formatting consistent with existing sections
-
Use doc comments in code:
- Document public APIs with
/// - Add module-level documentation with
//! - Explain complex algorithms in comments
- Document public APIs with
-
No temporary documentation files:
- Don't create separate guide files
- Use TODO comments in code instead
- Use git commit messages for development notes
Good prompt:
"Write a Rust function to parse a string into integers. The function should:
- Use safe Rust (no unsafe)
- Return Result<Vec<i32>, &'static str> for error handling
- Validate all inputs
- Be simple and easy to understand
- Include doc comments
- Follow Rix coding standards"
As Rix evolves, keep in mind:
- Heap allocation: Plan for adding the alloc crate
- Async/await: Consider async I/O for future features
- Multi-tasking: Design with multitasking in mind
- Safe abstractions: Always wrap unsafe code in safe APIs
- Testing: Add unit tests when possible
When in doubt:
- Prefer safe Rust over unsafe
- Prefer simple over clever
- Use the type system to prevent bugs
- Add comments when code isn't obvious
- Test thoroughly
Remember: This is an educational OS. The goal is to learn and teach safe systems programming. Clarity, correctness, and safety above all else.
- OSDev Wiki
- Intel 64 and IA-32 Architectures Software Developer Manuals
- Writing a Simple Operating System from Scratch
- The Rust Programming Language
- Rust Embedded Book
- Writing an OS in Rust
Created as an educational project to demonstrate memory-safe operating system development using Rust.
