What is this series even about?

I have picked up the project of writing an operating system (mostly just a kernel right now) in Rust. This is something I’ve attempted a few times before, and even got pretty far in some key areas. However, due to my less than fantastic handling of my own personal data, most of those projects are lost to time. So this time around, I’m doing it from scratch, and keeping track of all my data the right way. So, I’m restarting from scratch. This is probably what I would have done anyways, given how far the rust OS ecosystem has come since my last real runthrough. So, this series of posts will serve as a “do-torial” so to speak, where I write about what I’m doing as I’m doing it, but it won’t be a tutorial so much as giving code and walking through it. However, I believe if you come back to this series in 3 years time from when I’m writing this very paragraph, and go through it while really trying to understand everything I say, you too could walk out of it with your own operating system to show to all of your cool hot friends.

The hardest concept to grasp in Operating Systems.

A lot of my projects failed before they even started simply because I failed to grasp one key concept: Memory is yours to do what you will with it. There’s no right place to put your kernel, or your stack, or modules, or any of the other million components you’ll have to map into memory. You can simply just decide. Have fun with it! But, keep a directory saved somewhere so you know what addresses mean what. For example, my kernel heap starts at virtual address 0o777777_420_420_420_000_0000, which was super easy to recognize while I was having trouble expanding my heap. additionally, most MMIO devices are mapped starting at 0xFFFF_B00B_1320_0000, which as immature as it may seem, does make it just that little bit more fun to write.

Getting started.

To be honest, I just yoinked and twisted the boot example from rust-osdev/bootloader, and added a few nice qemu flags that I like. There are absolutely arguments to be made for using more tried and true bootloaders like GRUB or Limine, but this was simple, and I plan to take up the task of adding multiboot support to the project once it feels relevant anyways. For now, this works great. Not much to be said here.

First things first.

I wanted to get some basic logging working, so I brought in uart_16550, a pretty simple crate that allows for uart communication. Crazy how that works. Writing a simple wrapper class that implements core::fmt::Write, and adding -serial stdio to my qemu flags, I was able to recieve logs from my kernel. Success!

Next, it’s time for some basic initialization. I had bootloader setup paging as a recursive page table, with the recursive index as 511. This is probably the most common place to throw it, just because it’s easier to remember. I also setup a GDT with both kernel and user segments, in contrast to the exclusively kernel ones in bootloader’s gdt. This will be important way later down the line.

I also setup a very basic IDT that just panics with the stack frame for any given exception, given my plan for this initial “kernel” is to setup some basic data structures, then jump into the first of many modules that will act as the complete kernel.

I then implemented a basic physical frame allocator that just keeps a bitmap of all used frames. Setting this up goes as follows.

  • find out the total size of physical memory.
  • bitmap_size = total_mem_size / 4096 / 8
  • manually find enough frames for that bitmap_size and map that into the address space.
  • run through the MemoryRegions structure passed by bootloader and mark all used pages as used in the bitmap
  • profit

Finding and freeing frames with this bitmap model is pretty simple, So I won’t describe that process here.

Additionally, I wrote a kernel heap using a buddy allocation system, that uses a header at the top of each buddy to keep track of everything.

Finally, I wrote a very basic hello world module, gave it its own address space and jumped into it, effectively leaving the bootstrapping kernel. This first module’s name is “delegator”, and we’ll go into its function in the next post, whenever that comes around.

Thanks for reading! bye now :)