Writing an operating system
If you follow me on Twitter, you may remember that I’m writing an x86-64 operating system in Rust to learn about operating systems. I learn best by doing and by writing. I’m sharing my work on GitHub. I’ll use this blog to share my writing.
So far, Georgix—that’s my operating system’s name, because I’m not creative enough to come up with something less obnoxious—boots, configures periodic timer interrupts, and handles them by printing dots to the screen. That’s not much, of course, but I’ve already learned a ton and I’m looking forward to learning more. Next up is memory allocation and paging.
If you’re like me, you want to start from the beginning and read forward. GRUB hands off to Georgix here. The bootstrap code puts the processor in 64-bit long mode, then jumps into Rust. Execution continues here.
I’m following Philipp Oppermann’s excellent Writing an OS in Rust series, but I’m going off-book to explore areas that interest me more deeply. For example, Philipp provides a boot loader written specifically for the tutorials. Its primary advantage is that it doesn’t require setting up a cross-compilation toolchain, which can be fussy and frustrating. But once I got Georgix booting with the provided boot loader, I was interested in learning more about the bootstrap process and using a more complete and portable boot loader like GRUB. So I followed the first edition of Steve Klabnik’s intermezzOS book to boot from GRUB.
Philipp’s tutorial also uses the 8259 PIC to set up timer interrupts. The 8259 is simpler to program against, but it is outdated. It’s superseded by the APIC in newer x86-64 processors, though they emulate the 8259 interface for backwards compatibility. (Philipp covers this, to be clear.) Moreover, APIC support is required to implement multiprocessing, something I’d like to explore eventually. After reading about how to use the APIC, I decided to make the switch. I’ll cover that in a future post.
Finally, Philipp provides a crate that takes care of building system data structures like the Interrupt Descriptor Table, Global Descriptor Table, and Task State Segment. The
x86_64 crate is excellent, and these data structures constitute some of the more tedious paperwork in operating systems development, but I wanted to get my hands dirty. I imported Philipp’s IDT, GDT, and TSS structures into Georgix line by line, with tiny, idiosyncratic changes to suit my tastes.
Somewhere in all of that, we launched HEY at work and I took a break from hobby programming to recover. Now I’m on sabbatical for the month of August. I’d like to get through the remainder of Philipp’s tutorials before I return to work.
I was inspired to start this project by Xv6, a reimplementation of sixth edition Unix in modern C. I first learned of Xv6 when Abhijit Menon-Sen’s article on how Unix pipes are implemented appeared on the front page of Hacker News. Xv6 and Menon-Sen’s piece showed me that real-world operating systems could be far more approachable than I had considered.