Starting with Limine for x86_64
In order to start writing a kernel you need to be able to boot it, luckily the Limine Bootloader and the Limine Protocol makes it very easy to start having code run under ring 0 and start making your own kernel.
The Limine Protocol is what you can use with your kernel to have it be able to be booted by any bootloader implementing it such as Limine and it will provide you a way to get many things you need to get your kernel running with stuff like a basic framebuffer and a memory map and a way to load files into memory to be read by your kernel to be something like an Inital Ramdisk. This lets you get started making your kernel instead of slogging through making a bootloader.
The Easy Way
Limine provides templates for starting your kernel in C and C++ and using C with Meson if you dislike makefiles. You should be able to clone one of these repos and start working on your kernel and adding your own code to set everything up.
Please remember to read the Limine Protocol to know how the Limine Bootloader will leave the system and how you will need to set it up and what other features you are able to use while using the Limine Protocol.
The Hard Way
This section is mainly a retelling and slightly simpler setup of The Easy Way and that way is highly recommended, it is a much better starting point and you should be able to edit the structure of the template if it is not to your liking.
This is just provided for people who wish to DIY and learn more about how using the Limine Protocol works BUT you should use The Easy Way after getting this to work to have a much better environment working with your kernel. The template can also be adapted into an existing project if you have given up making your own bootloader because it was causing too many issues.
Main C File (main.c)
Your main C file with your entry point should minimally look something like this
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | |
Linker Script (x86_64.lds)
To compile your kernel properly you need a linker script to tell the linker how to format the final binary and have all the .o files properly put together
It should look something like this when you are starting out although it can be expanded
Compiling & Running in QEMU
You can use the following command to compile the kernel
gcc -march=x86-64 -mcmodel=kernel -mabi=sysv -ffreestanding -mno-red-zone -fno-stack-protector -fno-stack-check -fno-lto -fno-PIC -c main.c -o main.o
- -march=x86-64 [Tells GCC to output at x86-64]
- -mcmodel=kernel [Tells GCC to apply settings specific to kernel applications]
- -mabi=sysv [Tells GCC to use the System V ABI]
- -ffreestanding [Tells GCC there is no libc (Bare Metal)]
- -mno-red-zone [Tells GCC the red zone is unsupported]
- -fno-stack-protector [Disables some stack protections as it requires libc support]
- -fno-stack-check [Disables some stack protections as it requires libc support]
- -fno-lto [Disables Link Time Optimization]
- -fno-PIC [Disables generating Position Independent Code]
You can use this command to link the kernel into the final .elf file
The flags used here do the following- -T x86_64.lds [Use the linker script that you made before]
- -m elf_x86_64 [Tell the linker to output an x86_64 elf file]
- main.o [Tells the linker all the .o files to link together. Put all .o files here]
- -o kernel.elf [Specifies the output file]
You should then copy the Limine bootloader .efi file to /EFI/BOOT/BOOTX64.EFI in your project directory which can be found at /usr/share/limine/BOOTX64.EFI if you use an arch based
distro with the Limine Package
As well with the EDK2 OVMF Package on an arch based distro you will need to copy /usr/share/edk2/x64/OVMF_CODE.4m.fd
and /usr/share/edk2/x64/OVMF_VARS.4m.fd to your project folder
Then you need to make a basic limine.conf file as shown below
Finally you should be able to run the following QEMU command to boot your kernel (Under linux using KVM)
qemu-system-x86_64 \
-machine q35,accel=kvm \
-cpu host \
-m 512M \
-drive if=pflash,format=raw,readonly=on,file=./OVMF_CODE.4m.fd \
-drive if=pflash,format=raw,readonly=on,file=./OVMF_VARS.4m.fd \
-drive format=raw,file=fat:rw:. \
-boot d
- -machine q35,accel=kvm [Virtualize a machine with a modern chipset and use KVM]
- -cpu host [Virtualize a CPU that is equal to your host CPU]
- -m 512M [Give the VM 512MB of memory]
- -drive if=pflash,format=raw,readonly=on,file=./OVMF_CODE.4m.fd [Enable UEFI]
- -drive if=pflash,format=raw,readonly=on,file=./OVMF_VARS.4m.fd [Enable UEFI]
- -drive format=raw,file=fat:rw:. [Use the current directory as the VMs disk]
- -boot d [Boot the first virtual disk]
Rust and Zig
There exist some third parting bindings for using the Limine Protocol with these two languages if you desire.
Writing a kernel in a language other than C is possible but this wiki as of now will only provide examples in C and if you are using another language you will have to translate the examples accordingly, But there is rust code in even the linux kernel and it is very possible to make a kernel using only rust (and assembly). So any alternative compiled languages should be able to be used to make your kernel if desired.
Note: These templates are not official and have not been tested by the wiki team but they should work fine and be ok