Part 2 - The Minimal way of kernel
Part 2 - The Minimal Way of My Kernel
In this installment, I’ll walk you through the bare essentials of booting and running a tiny bare-metal Rust kernel. I’ve tweaked and annotated Phil Opple’s outline to reflect my own experiments—and added callouts where pitfalls might hide for begineers.
The Boot Process
When you hit power, your hardware embarks on a well-orchestrated journey called the boot process. Here’s how it goes:
Keep your motherboard manual nearby—firmware behaviors can vary slightly between BIOS and UEFI implementations.
Step 1: Power-On Self Test (POST)
- Firmware (BIOS/UEFI) lives on a chip affixed to the motherboard.
- It kicks off POST checks:
- Verifies CPU sanity
- Confirms RAM integrity
- Detects keyboard, display, and other essentials
- Initializes chipset, buses, fans, etc.
Step 2: CPU & Hardware Initialization
- Firmware loads minimalist drivers to talk to hardware.
- On BIOS, the CPU stays in 16-bit real mode.
- On UEFI, you often start in protected or 64-bit long mode.
Real mode has only 1 MB of accessible memory! You’ll need to switch modes for anything complex.
Step 3: Finding a Boot Device
- Firmware scans drives (HDD/SSD, USB, network) by a defined priority.
- It loads the first bootloader, which then pulls in your OS kernel.
In this guide, we’ll stick to BIOS and leverage the bootimage
tool so we don’t reinvent the bootloader wheel.
The Multiboot Standard
The Free Software Foundation’s Multiboot spec (used by GRUB) defines how to load a 32-bit protected-mode kernel. It requires extra work for 64-bit, and its docs are terse.
We’ll defer full Multiboot support until later—bootimage
gives us a simpler path now.
A Minimal Kernel
Goal: Build a disk image that prints “Hello from KaranOS!” to the screen using a freestanding Rust program.
1. Rust Nightly
We need Rust nightly for unstable features used in OS dev.
rustup override set nightly
rustc --version # should end in "-nightly"
Nightly changes fast—pin your toolchain in rust-toolchain.toml
if you hit unexpected breakages.
2. Custom Target JSON
Create x86_64-karan_os.json
in your project root:
{
"llvm-target": "x86_64-unknown-none",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-" +
"i64:64-i128:128-f80:128-n8:16:32:64-S128",
"arch": "x86_64",
"target-endian": "little",
"target-pointer-width": "64",
"target-c-int-width": "32",
"os": "none",
"executables": true,
"linker-flavor": "ld.lld",
"linker": "rust-lld",
"panic-strategy": "abort",
"disable-redzone": true,
"features": "-mmx,-sse,+soft-float"
}
A single missing comma or brace will break cargo build
—watch your JSON syntax carefully.
3. Building the Kernel
Start with a minimal src/main.rs
:
#![no_std]
#![no_main]
use core::panic::PanicInfo;
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
#[no_mangle]
pub extern "C" fn _start() -> ! {
loop {}
}
cargo build --target x86_64-karan_os.json
The build will complain because core
isn’t prebuilt for our target. Fix it via .cargo/config.toml
:
[build]
target = "x86_64-karan_os.json"
[unstable]
build-std = ["core", "compiler_builtins"]
build-std-features = ["compiler-builtins-mem"]
And install sources:
rustup component add rust-src
build-std
forces recompilation of core crates—expect longer compile times.
4. Writing Text to VGA
We’ll display “Hello from KaranOS!” on the VGA text buffer at 0xb8000
.
#![no_std]
#![no_main]
use core::panic::PanicInfo;
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
static MESSAGE: &[u8] = b"Hello from KaranOS!";
#[no_mangle]
pub extern "C" fn _start() -> ! {
let buffer = 0xb8000 as *mut u8;
for (i, &byte) in MESSAGE.iter().enumerate() {
unsafe {
*buffer.offset((i * 2) as isize) = byte;
// 0x1f = bright white on blue background
*buffer.offset((i * 2 + 1) as isize) = 0x1f;
}
}
loop {}
}
We’re using unsafe
here for raw pointer writes. In the next post we’ll wrap this in a safe VGA writer.
5. Bootimage & Booting
Add to Cargo.toml
:
[dependencies]
bootloader = "0.9"
Install and prepare:
cargo install bootimage
rustup component add llvm-tools-preview
Generate the image:
cargo bootimage
Run in QEMU:
qemu-system-x86_64 \
-drive format=raw,file=target/x86_64-karan_os/debug/bootimage-karan_os.bin
When writing to real hardware via dd
, double-check your of=
target or you might overwrite your main OS!
What’s Next?
In Part 3, we’ll build a safe abstraction for VGA text and introduce a rudimentary println!
macro. Stay tuned!