inici(os)
Introduction: The Problem
The growing complexity and size of modern software is a significant issue, which I have discussed previously here.
Today, software tends to be bloated, with enormous and complex codebases that can consist of tens of millions of lines of code and intricate dependency trees, often just to perform simple tasks.
It wasn’t always like this. In the past, when computers had limited processing power, developers had less freedom and were compelled to solve problems in more efficient ways.
Modern Software Design
Modern software design is fundamentally flawed. Theories such as Object-Oriented Design and Domain-Driven Design often fail in practice because they do not align with how computers actually work. The domain should be the memory, and the design should focus on code and data structures.
These theories often introduce new problems, including maintainability issues, poor performance, lack of security, and degraded user experience.
Kernel Design: Drivers
Whether developing monolithic or microkernels, much of the focus seems to be on driver development rather than actual innovation.
When something doesn’t work, the solution is often to write a new driver. This approach creates a bottomless pit of complexity, as there will always be new hardware to support, leading to more and more lines of code.
Rationale: How to Solve it
Proper Design: Bottom-up Approach
Many developers have the mindset of adding layers on top of existing systems to fix problems. This approach is not inherently wrong but cannot be applied universally.
An analogy is when something breaks, you have two choices: patch it up with duct tape and hope it holds (and add more tape if it doesn’t), or evaluate why it broke in the first place. Maybe the materials were unsuitable, or it was used incorrectly. We might need to create a new component to handle the task properly.
While this may seem simplistic, it illustrates that modern software often uses tools that are not ideal for the job, leading to suboptimal solutions.
To effectively solve these issues, we must evaluate our choices from the ground up, avoiding unnecessary bloat.
If “A” does “X” better than “B”, use “A”
This principle is self-explanatory: we should use the best tool for the job. By doing so, productivity is increased, and we avoid relying on multiple suboptimal software projects.
For example, if I want to display the contents of a file, I should use the best tool available for that task. The same applies to editing a file, etc.
Keeping It Stupid Simple (KISS)
A key principle in writing good software is to keep it simple. We should always seek the simplest way to achieve our goals. The philophy is: “if something can be simplified, it will get simplied”.
This principle applies to both the design and implementation stages. For example, instead of performing extensive computations, we might pre-calculate and store data for quick access.
It’s also crucial to stay focused when developing a feature and avoid adding unnecessary functionalities. This aligns with the original UNIX design principles.
How Many LOC Does It Take to Do “X”
Another important consideration is achieving functionality with the fewest lines of code possible, without sacrificing readability or performance.
OS Specific
These design philosophies still apply to other software, but here I am talking specifically about Operating Systems.
Bare-Bones Bootloader
Avoid multi-stage bootloaders and complex boot protocols (unless absolutely necessary).
No initial ram disk or anything similar. The goal is to boot as fast as possible with as less drag as possible.
Modular Design
This principle tells us to break the problem down in well-defined, independent modules each with a single responsability and communicate through simple and well-defined interfaces.
We need to separate the kernel, filesystem, multitasking, hardware interaction, etc. Set as a goal: a ridiculous low amount of lines of code to do “X”. The less you write, the better (without sacrificing performance, of course).
Zero Drivers
As previously mentioned, drivers are a major source of complexity in modern operating systems. We could look to embedded systems for inspiration on how to build an OS with zero drivers.
“Restrictive” multitasking
Multitasking is not inherently bad, but modern operating systems often run too many tasks simultaneously. For a consumer/desktop OS, I might only want to run 2 or 3 tasks at a time. This allows us to simplify the pre-emptive multitasking algorithm while still supporting multiple tasks when necessary.
Different OS for different purposes
I don’t want a general-purpose OS; I want an OS tailored to my specific use case. For example, if I’m using it on a desktop, I don’t need it to function as a server OS. This specialization simplifies the entire OS stack.
Small OS layer
The OS layer should be as minimal as possible. While POSIX provides a standard interface, it may be too large for what I intend.
Only the essential features should be implemented in the kernel and non essential features should be moved to userspace. In theory this simplifies kernel development and improves security (which may be irrelevant considering other design choices).
Use static linking for the kernel to avoid the complexity associated with dynamically linking a kernel.
Single User
Focus on single-user environment. This provides a great oportunity to simplify security and permission models.
Conclusion: thoughts and moving forward
ARM and RISC-V: a new oportunity
With the advent of new ARM SoCs and RISC-V processors, there is a tremendous opportunity to apply these principles and develop more efficient operating systems.
If I am aiming for “zero drivers”, my OS will be dependant on a specific computer, which immediately removes thousands of esoteric hardware written in the kernel.
If then a user wants to communicate with other external hardware, more software will be developed; I dont consider it a “driver” simply because it wont be written at the kernel space but instead at the user space.
“But what does that solve?” You ask. Well, by writing this external modules outside of the kernel to communicate with your hardware it means the kernel will always remain slim with no extra driver bloat you dont need.