reedos: (A) Reed Operating System
Myself and Ethan McDonald (of the same year at Reed) wrote this operating system over a semester as a way to apply our skills from an Operating Systems class we were taking at the time. It is written entirely in Rust and targets the RISC-V platform. While hardly fully featured, it served as a base for experimentation for that semester and the following summer. The github repo can be found here.
In hindsight, the choice of Rust was perhaps a little strange. Neither of us had any prior Rust experience, and while it is certainly possible to write at least semi-idiomatic Rust on bare metal, it takes a level of familiarity with the Rust compiler’s expectations that was simply not present. Despite that, I’m still fairly happy with the results. The code reads as a very strange fusion of pointer fiddling as one would do in C and the higher level iterators and the like more familiar to the average Rust program. Besides simple ignorance, I don’t think I would recommend an operating system as an early Rust project, nor Rust as a language to write an operating system in. A number of the subsystems and problems endemic to operating systems are extremely poor matches with Rust’s borrow checker and more profoundly with the single ownership story that supports its formalisms. Who owns a process in a process table? A file descriptor for a file opened by a process, or heaven forbid several processes? How do you convince the compiler that you know what you are doing when writing a page allocator? I don’t think these are fundamentally impossible in the Rust view of the world, but the safety one earns from all this homework combing various lifetimes to all point in one direction, wrapping up this and that bit of interior mutability into managers and systems, owners and paradigms, feels to me less and less appealling. All the more so when the canonical references to take ideas from (System VI for instance), have truly elegant if radically unsafe implementations in C. Need a free list for your unused physical pages? Simply keep it in the pages themselves!
It’s a common catch-phrase of some Rustaceans that one should wrap up the unsafe or unreliable parts of the system, and present the safe ones outward and upward. When possible, either by forgiving problem structure or lots of legwork by clever people, it’s certainly lovely to leave the messy bits on some lower level and work primarily or exclusively with the happy path, the core ideas. I can certainly throw no stones about the so-called Tower to Heaven of increasing abstraction, given my propensity for Haskell. But in the context of operating systems, where the guarantees one is given by the hardware are so sparse and look so different from this nice value and lifetime view of the world, it can feel somewhat silly to spend all this time placating the compiler when it can feel as though one is building on sand to start with. Doubly so when the solutions can begin to reek of excess object orientation. Does this PageTableProxyFactory really represent a meaningful subsystem of my program? Does this OnceCell<Arc<ResourceTable<’b>>> really have a meaningful lifetime? I can’t call it static without breaking the idea of "If I have the value I know it works", since it requires initialization, but once I have one I will never make another, and it will survive until the end of my program. I wonder in moments if the abstractions really apply. Certainly it’s expressible, but that’s not enough, safety is not worth an infinite amount of awkward encodings and representations in my mind.
All that said I will be the first to admit that I am far from a maestro of Rust. Perhaps a more enlightened programmer than I would produce representations that are neither awkward nor struggle to satisfy the compiler’s requirements. From my vantage point of C and Haskell, Rust strikes me as an gangly middle child. Why are there both Into and From traits? Why are multiparameter traits not included? What is pointer providence; why, and how has it changed so many times? If the compiler can recommend me the exact source code change to fix me passing a mut reference into a const one, why can’t I turn on a flag to make it just shut up and do it for me? Why is integrating with other languages at build time so annoying if the Rust project isn’t the top level one?
C is certainly old and weird, but it remains the Lingua Francia for a reason. Lot’s of ink has been spilled about how the flat memory model doesn’t accurately represent the way computers work today, but until I see a language with compile time markers of what is likely to be in L2 cache I’ll continue to be an extremely skeptical listener. Were I to write something that needed the sort of nicer-than-C pleasures of vectors and iterators but still some memory control, I think I would reach for Zig these days. Never really gotten acquainted, but it looks lovely. Perhaps I’ll write an operating system.