Ever since working with the xv6 kernel for MIT’s 6.828 “Operating System Engineering” labs (which are publicly available, see my previous blog post), reading its source and extending some of it functionality, I have been wanting to start writing my own hobby kernel from scratch. I never felt quite ready, so I kept looking at other hobby OS projects and read many blog posts. Finally, I decided to just start working on my own recently, what could go wrong? Well, many things can go wrong in OS dev, more on that soon, but I encourage anyone interested in kernel development to just go and start writing their own without waiting “to be ready” for too long.
Since I am most familiar with xv6’s design and RISC-V architecture being more approachable than x86, I opted for targeting qemu’s riscv64 virt machine, just like xv6. There are even a few xv6 ports to Rust already, which is the implementation language of my choice, so I’ve got enough source code to look at when I get stuck. However, I am trying to do that as little as possible, for it is far too easy to start copying things verbatim. To avoid my implementation being just an exact clone, I am also diverging from xv6 in some ways, for example I make use of OpenSBI, a sort of hardware abstraction layer that provides a [S]upervisor [B]inary [I]nterface to the kernel, analogous to what an ABI and in particular system calls are to user space. This makes interacting with hardware easier, instead of bit fiddling memory-mapped peripherals, one can just do function calls (e.g debug print via UART). See this great article by Uros Popovic to get a proper introduction to the SBI standard.
Besides the SBI functionality itself, OpenSBI provides a pointer to a structure describing the machine’s hardware to the kernel by passing it in a register before calling into the kernel code. This structure is called a devicetree and I use it to obtain the number of cores and amount of memory of the machine dynamically. In contrast, xv6 and many other small hobby/educational kernels use static values for these at compile time, which simplifies initialization code and general design. It seemed like a cool first thing improve upon compared to the kernels I had looked at. Well, this divergence from xv6’s design is also the root cause for the first gnarly bug that left me bewildered for quite a while until I realized what was happening…