Writing an OS in pure Rust
Over the past six months we've been working on a second edition of this blog. Our goals for this new version are numerous and we are still not done yet, but today we reached a major milestone: It is now possible to build the OS natively on Windows, macOS, and Linux without any non-Rust dependendencies.
The first edition required several C-tools for building:
- We used the
GRUBbootloader for booting our kernel. To create a bootable disk/CD image we used the
grub-mkrescuetool, which is very difficult to get to run on Windows.
xorrisoprogram was also required, because it is used by
- GRUB only boots to protected mode, so we needed some assembly code for entering long mode. For building the assembly code, we used the
- We used the GNU linker
ldfor linking together the assembly files with the rust code, using a custom linker script.
- Finally, we used
makefor automating the various build steps (assembling, compiling the Rust code, linking, invoking
We got lots of feedback that this setup was difficult to get running under macOS and Windows. As a workaround, we added support for docker, but that still required users to install and understand an additional dependency. So when we decided to create a second edition of the blog - originally because the order of posts led to jumps in difficulty - we thought about how we could avoid these C-dependencies.
There are lots of alternatives to
make, including some Rust tools such as just and cargo-make. Avoiding
nasm is also possible by using Rust's
global_asm feature instead. So there are only two problems left: the bootloader and the linker.
A custom Bootloader
To avoid the dependency on GRUB and to make things more ergonomic, we decided to write our own bootloader using Rust's
global_asm feature. This way, the kernel can be significantly simplified, since the switch to long mode and the initial page table layout can already be done in the bootloader. Thus, we can avoid the initial assembly level blog posts in the second edition and directly start with high level Rust code.
The bootloader is still an early prototype, but it is already capable of switching to long mode and loading the kernel in form of an 64-bit ELF binary. It also performs the correct page table mapping (with the correct read/write/execute permissions) as it's specified in the ELF file and creates an initial physical memory map.
The plan for the future is to make the bootloader more stable, add documentation, and ultimately add a “Writing a Bootloader” series to the blog, which explains in detail how the bootloader works.
Linking with LLD
With our custom bootloader in place, the last remaining problem is platform independent linking. Fortunately there is
LLD, the cross-platform linker from the LLVM project, which is already very stable for the
x86 architecture. As a bonus,
LLD is now shipped with Rust, which means that it can be used without any extra installation.
The new Posts
The second edition is already live at https://os.phil-opp.com/second-edition. Please tell us if you have any feedback on the new posts! We're planning to move over the content from the first edition iteratively, in a different order and with various other improvements.
Many thanks to everyone who helped to make Rust an even better language for OS development!