-
Notifications
You must be signed in to change notification settings - Fork 0
Booting the kernel
The GRand Unified Bootloader implements a specification called Multiboot, which is a set of conventions for how a kernel should get loaded into memory. By following its specifications, we can let GRUB load our kernel. We achieve this using something called a "header". It contains information following the Multiboot format, which GRUB will read and follow. To create the kernel binary which will be loaded by GRUB you will need:
The header is defined in the boot.s file where its also described with comments
This header then needs to be assembled with nasm
:
nasm -f elf64 multiboot_header.s
-f elf64
: Specifies ELF output file format, i.e how framework defining how exactly the bits will be laid out in the object file, multiboot_header.o
.
Note: Building with no main is a Rust Nightly feature!
main.rs
:
#![no_std]
#![no_main]
use core::panic::PanicInfo;
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
#[no_mangle]
pub extern "C" fn kernel_main() -> ! {
loop{}
}
Since we are compiling for kernel space, where there is no such thing as a main function, we specify #![no_main]
as global attribute, letting Cargo know not to look for it. Instead, we have the _start()
symbol, which we will later reference in the linking process. Rust also requires us to have a panic handler, defined with the #[panic_handler]
local attribute.
Cargo.toml
:
[package]
name = "kfs"
version = "0.1.0"
edition = "2021"
[profile.dev]
panic = "abort"
[profile.release]
panic = "abort"
Nothing crazy here, just specifying the panic behavior, since it will not be set by default due to #![no_std]
.
.cargo/config.toml
:
[unstable]
build-std = ["core"]
[build]
target = "x86_64-unknown-none"
This is our Cargo config, specifying which crates are to be included into our binary. The [build]
directive sets the build's target architecture, x86_64
, and unkown-none
since we are building a custom kernel.
We will need to link our multiboot header and our actual kernel code into one final executable - we need cargo to give us an object file (.o
). This can be achieved using Cargo's rustc sub-module, and telling it to emit mentioned object file:
cargo rustc --release -- -emit=obj
This will output our object file into target/x86_64-unknown-none/release/deps/kfs-*.o
.
We will now link multiboot_header.o
and kfs-*.o
into one binary. For this, we will create a linker script called linker.ld.
We can now use this script to link our binaries like follows:
ld --nmagic --output=kernel --script=linker.ld multiboot_header.o kfs-*.o
--nmagic
:
- Turns off automatic page alignment, reducing the binary file's size.
iso/
|___boot
|___grub
| |___grub.cfg
|___kernel
iso/boot/grub/grub.cfg
:
set timeout=0
set default=0
menuentry "kfs" {
multiboot2 /boot/kernel
boot
}
This file configures GRUB. GRUB lets us load different operating systems, displaying a menu of choices on boot. Each menuentry
is one of those choices.
iso/boot/kernel
is just the binary we created in the linking process.
We can now create our .iso
file using grub-mkrescue
:
grub-mkrescue -o kfs.iso ./iso
To run our kernel, we use QEMU. QEMU is a system emulator. Run:
qemu-system-x86_64 -cdrom kfs.iso -boot d -nographic
x86_64
:
- Specifies the QEMU variant.
-cdrom kfs.iso
:
- Start QEMU with CD-ROM drive, its contents being
kfs.iso
.
-boot d
- Makes QEMU boot from CD-ROM directly instead of trying Hard Disk and Floppy first.
-nographic
:
- Runs the kernel in text mode.