Design of the DragonWare Operating System

This page talks about the design of DragonWare as an operating system, from the bootloader, to the kernel, the shell and everything else. It is intended to give readers a better view of the project and what makes it different. In other words, why DragonWare is not Yet Another UNIX Clone.
This page is not a technical paper nor a proof of anything. Consider it more in the style of a blog or an informal relic of how DragonWare came to be. As always, you're very welcome to ask any questions you might have.

Bootloader

An operating system needs to be loaded into memory by the computer's firmware. Because of restrictions in the way a regular PC boots, this is often offloaded to a separate piece of software called a bootloader, with the main responsibility being to load the kernel in memory and, in DragonWare's case, also load the startup drivers (microkernel servers).
For months before DragonWare was first published, the popular bootloader called GRUB as well as Limine were used. Now, on a personal note, I think writing my own bootloader ended up being a waste of my time, I learnt very few things and the opportunity cost with respect to kernel development was significant (haha look at me I took an economics class). But the motivations were better integration with the operating system (DragonWare is developed in a monorepo, like the BSDs), significantly less bloat and most importantly, compatibility with pre-Pentium II hardware (at the time, I wanted to make DragonWare run in older hardware exclusively). It is still possible to use DragonWare with other bootloaders, but less attention is given to them.
But in the end, I learnt a few things, and I'm proud of this effort, even as hackish as it feels to write it in the first place.


Microkernel

DragonWare is built around a minimal, simple microkernel. A microkernel is an operating system kernel which is intentionally limited in functionality and features, offloading much of the traditional kernel roles to userspace servers (more on that later...). A commonly cited example is Mach, on top of which GNU/Hurd is built.
The advantages of microkernels are many, but to summarize them in a quick way:


libdwuser

On top of the microkernel the dwuser library is built. This library forms the basis of every application compiled for DragonWare. It provides access to kernel APIs, types, useful utilities and cleaner, native functions to perform common operations. It is intended as a native, clean replacement over the shortcomings of libc and other Unix-inspired libraries, with all batteries included to help developers.
A major motivation for DragonWare is what I perceive as legacy baggage in traditional C libraries to fix problems. I wanted to start from scratch, see how can I create a modern API set to develop native applications. For example, libc, being influenced heavily from Unix, is designed around the concept of "everything is a file". DragonWare doesn't understand "files" in and of itself. It doesn't make sense to force this paradigm in applications. dwuser is precisely the result of this idea.
I strongly believe that not EVERYTHING is a file. Your graphics card is not a file. It is a piece of hardware. It deserves its own mechanisms for access. Your headphones are not a file. They play sound. Unix tried to work around this with sysctl, and standardization broke down as hardware moved on and improved (let's not even talk about clones having their own drivers and therefore their own sysctl opcodes for each device).


Multiserver architecture

As mentioned, the kernel is intentionally "dumb". It doesn't know anything other than switching processes and sending messages around*. But then, how would you open a file, or write some text on the screen? What about my graphics card?
On top of the aforementioned libdwuser and the microkernel, DragonWare provides the so called "servers". A server is a process, often with special hardware access rights, that provides applications with a necessary service. Using message passing, the clients send messages to servers to request something from them. Let's see a simple example flow of printing something to the screen, step by step as it happens internally. Program A wants to write the text "hello world" on the screen:

  1. DragonWare loads at boot time a server binary. It has no idea what that binary is or what it does, it just immediately loads it and prepares it to run.
  2. When the binary is run, it creates a global port named CONSOLE. Other processes can now open this port and send messages to it.
    Quick clarification - Sending messages "to a process" is not a thing. All IPC is done using ports. A process exposes a port, another process reads messages from this port when they arrive.
  3. Program A opens a connection to the port CONSOLE provided by the console server*.
  4. Program A now sends a message to port CONSOLE, following the server's protocol, telling it that it wishes to print the string, along with how many bytes to print.
  5. The console server is woken up. It sees a message has arrived by a client, and reads the parameters of this message (what is the client asking for, permissions, etc).
  6. It sees that the client wants to print something on the screen. It agrees to do so, and writes the characters of this message to the screen.
  7. The user now sees somewhere on the screen "hello world". The console server sees no more messages, and temporarily goes back to sleep.
Notice how the kernel took zero action here, besides passing the messages around. This was purely done in userspace, by regular programs.
  1. * The kernel actually contains a little more functionality than that. Boot message drivers, object handling, memory management, and others. But for the purposes of this article, we can ignore them.
  2. * The console server isn't one thing - Two implementations exist in DragonWare, one built on top of a generic graphics driver. And because of microkernel design, it is possible to write a separate implementation.


libc

On top of the former two, libc is implemented. Usually by wrapping around dwuser APIs. The most important parts of libc are the everyday functions taken for granted: memcpy(), printf(), exit(), et al. Despite the opinionated takes on it in the section on dwuser, it is still essential to a complete operating system that DragonWare aspires to be, for two major reasons:

  1. Porting applications in the future is going to be very easy, because all the necessary libc functions will be present.
  2. There's no reason to duplicate the code. Even the compiler expects a memcpy/memset implementation for its own internal semantics. While it can be all included in dwuser, it is better for modularity and DRY principles to keep them separate.

The most important thing here is that libc must remain 100% compliant to the official standard. C23 is considered the standard that DragonWare's libc implementation abides to (since DragonWare itself uses the C23 standard in its codebase), and all implementation requirements are to be followed to the letter. One exception is done for third party software which relies on nonstandard behaviour in many operating systems, but we have a long way ahead before this becomes a problem.
That being said, libc is not intended to develop applications for DragonWare. The dwuser library is used for anything more advanced that "print some text on the screen". It is only there for future compatibility and separation of concerns. Especially for performant I/O and fast data exchange locally, libc is basically a no-go. It translates into the DragonWare API internally and that induces performance costs. Besides, some functionality will always be missing.
A future libstdc++ is planned, to also support porting C++ applications. The aims will remain the same: Complete standard conformance to the fullest extent and no extensions or extra code than what's necessary to port applications.


dcp (DragonWare Command Prompt)

All operating systems, at least those targeting human users, need to provide a way to access the functionality presented by the operating system. DragonWare is no different in that aspect. In combination with everything previously mentioned, a shell has been developed, called a command prompt instead frankly because I liked the name better.
The shell receives text input from the user and converts it into machine readable instructions to be executed. There's more to it, but that's the gist of what a shell does. Because DragonWare has no disk drivers or filesystem support yet, it is limited to just a handful of builtin commands. Perhaps what makes dcp different as a shell is that very little functionality is provided by it. It parses commands, does something with them, and nothing more. No script execution (that will be offloaded in a separate engine depending on what scripting language DragonWare may support one day) and no arbitrary features like system configuration built into the shell's configuration. Lean and minimal, for seperation of concerns.


Miscellaneous