intro post

This commit is contained in:
janis 2026-07-16 13:15:15 +02:00
parent aa98b907f4
commit 83d1ce9b98
Signed by: janis
SSH key fingerprint: SHA256:bB1qbbqmDXZNT0KKD5c2Dfjg53JGhj7B3CFcLIzSqq8
2 changed files with 91 additions and 4 deletions

View file

@ -259,3 +259,90 @@ We will create a =run.sh= script to automate hide away the details of launching
-vga std
#+end_src
Lets go through the script step by step:
The first like after the shebang that tells the loader to use =bash= as the interpreter is =set -e=, which means the script will exit immediately if any command returns a failure exit code.
This means that, if we for example don't have limine installed, the script won't try to create the image or run qemu with an incomplete setup.
Our script currently takes a single argument, the path to the kernel binary.
Cargo allows us to set a "runner" command in the =.cargo/config.toml= file, which it will call with the path to the binary as the first argument instead of the calling the binary itself. We will make use of this feature to automatically start our kernel in qemu with the help of cargo.
We take the first argument and extract the proper parent directory and the filename and use both to construct the path to the image file we create in the next step:
#+begin_src bash
bin="$1"
canonicalised=$(realpath -- "$bin")
parent=$(dirname -- "$canonicalised")
bin_name=$(basename -- "$exe")
image="$parent/$bin_name.img"
#+end_src
Next, we check if a file exists at the path we constructed for the image file, and whether it is older than the kernel binary:
#+begin_src bash
if [[ "$canonicalised" -nt "$image" ]]; then
#+end_src
If so, we will want to (re)create the image, starting with the =limine.conf= file the limine bootloader will read to know where to find our kernel binary:
#+begin_src bash
cat > /tmp/limine.conf <<EOF
timeout: 0
serial: yes
serial_baudrate: 9600
/Kernel
protocol: limine
path: boot():/boot/kernel.elf
EOF
#+end_src
We also tell limine to use the serial port for output, which means any error messages will be printed in the terminal we run our script (or =cargo run= ) in.
Next, we create a 64 MiB image file filled with zeroes, and format it with =mformat= to create a FAT32 filesystem.
After creating the directory structure using =mmd=, we copy the relevant files, including the =BOOTX64.EFI= bootloader binary from limine into the respective paths in the image file using =mcopy=.
#+begin_src bash
limine_datadir="$(limine --print-datadir)"
limine_uefi_path="$limine_datadir/BOOTX64.EFI"
dd if=/dev/zero of="$image" bs=1M count=64
mformat -i "$image" -F ::
mmd -i "$image" ::/boot
mmd -i "$image" ::/EFI
mmd -i "$image" ::/EFI/BOOT
mcopy -i "$image" /tmp/limine.conf ::/boot/limine.conf
mcopy -i "$image" "$canonicalised" ::/boot/kernel.elf
mcopy -i "$image" "$limine_uefi_path" ::/EFI/BOOT/BOOTX64.EFI
#+end_src
Finally, we run qemu with the image attached as a drive:
#+begin_src bash
qemu-system-x86_64 \
-drive file="$image",format=raw \
-machine q35,accel=kvm -enable-kvm \
-drive if=pflash,format=raw,readonly=on,file="$OVMF_PATH/FV/OVMF_CODE.fd" \
-serial stdio
#+end_src
We tell qemu to use the =q35= machine type, which is a more modern virtual motherboard and is noticably faster at startup in my experience. Additionally, we tell qemu to use KVM acceleration, which will allow the virtual machine to run at near-native speed by utilising the CPUs hardware virtualization features rather than emulating the CPU in software. This requires that the host CPU supports virtualization and that KVM is enabled in the host linux system.
We also attach the UEFI firmware and the serial port we've told limine to write to.
Finally, we make a few changes to our =.cargo/config.toml= file to tell cargo to use our =run.sh= script as the runner for our kernel binary:
#+begin_src toml
[build]
target = "x86_64-unknown-kernel.json"
[target.x86_64-unknown-kernel]
runner = "./run.sh"
#+end_src
This tells cargo to use our custom target specification file by default when building, and to use our =run.sh= script as the runner for any binary built for our custom target.
If we now run =cargo run=, cargo will build our kernel and automatically start qemu.
Unfortunately, since our kernel currently does nothing, this is hard to discern.
Therefore, our next goal will be to get some output from our kernel, which we will do by making use of limines framebuffer.
* Getting the Framebuffer

View file

@ -11,7 +11,10 @@ bin_name="$(basename -- "$canonicalised")"
image="$parent/$bin_name.img"
cat > /tmp/limine.conf <<EOF
if [[ "$canonicalised" -nt "$image" ]]; then
echo "Creating image $image"
cat > /tmp/limine.conf <<EOF
timeout: 0
serial: yes
serial_baudrate: 9600
@ -21,9 +24,6 @@ serial_baudrate: 9600
path: boot():/boot/kernel.elf
EOF
if [[ "$canonicalised" -nt "$image" ]]; then
echo "Creating image $image"
limine_datadir="$(limine --print-datadir)"
limine_uefi_path="$limine_datadir/BOOTX64.EFI"