How to Create Your Own Operating System
Learn from diving into one of the most challenging endeavors in programming

In the modern-day, the average end-user rarely thinks about the operating system they use. In the mobile device market, according to the GlobalStats website, the Android umbrella dominates with a cumulative 73 percent market share while Apple’s ecosystem makes up the rest — meanwhile, if you look at personal computers, the picture is much the same with Microsoft systems taking the lion’s share of all systems in use.
Personally, I don’t much care for Windows and the closest viable alternative besides the Mac system I’m writing this on is Linux, which doesn’t even come close in day-to-day usability. It is widely considered impossible to overturn the present-day monopolies in the personal computer market — a sentiment that I both loathe and agree with — and I don’t aim to do the impossible.
However, I do hope to use this series of articles to show that creating even a very simple low-level program can be a fantastic learning experience for a programmer at any level.
Before we begin, I would like to say that this article will assume a level of familiarity with some basic and some advanced concepts in computer science and programming such as:
- Hexadecimal and binary notation;
- Data structures and their creation and manipulation;
- Algorithms;
- Assembly;
- As well as some more abstract programming concepts.
Getting started
We’ll start by setting up a cross-compiler. There are a few resources out there that will tell you how to do this for GCC, although I am going to write this series using LLVM and Clang as a base because we’ll later dive into some other programming languages as an aside.
You should already have Git and CMake installed as they are both used heavily in this series. If you need to download them, you may do so at the following locations:
The same applies to LLVM and Clang, and you may acquire them by running the following in a terminal:
If you do not already have Make, you could run CMake for a different generator like Ninja using -GNinja
, although Make is the default and comes with most Unix-based systems.
If you’re following this series on Windows, you have some options — you can install MinGW, a minimal environment that comes with GNU tools such as make, or you can make use of a WSL installation. Windows Subsystem for Linux is, as the name might imply, a full Linux system baked into your Windows install.
Once we have cleared these essentials, we’re ready to move on to the more exciting part of this story: the code!
Initial Boilerplate
The first thing we’re going to do is write some Assembly code. We don’t want to spend too much time in Assembly-land so we’re going to create the bare minimum of boilerplate in order to put us into our C environment.
We’ll first declare some constants for our multi-boot header:
These constants are some “magic” values that are thoroughly documented in the multiboot standard.
Next, we’ll define some sections. We need to make sure that the program identifies as a kernel, so we’ll start with our multiboot header and then BSS, and finally the Text section where we define our entry into our main function.
This code enables us to begin writing in a higher-level language of our choosing, which is C in this case. You may compile that first just to make sure that it works using Clang’s as
.
Firstly, we need to write out some utility functions. We need to make sure that we can write to the screen — something we’ll later implement using pixel buffers — using a combination of buffers and VGA color codes.
Now we can create a very basic shell:
And now we can finally write something!
We can write the main function that we referred to earlier and we can use our writeString
function to print something to our screen!
Now, we’ll need to link everything together:
To build our simple operating system, start by compiling the Assembly code. You may do so by running clang bootloader.s -o bootloader.o
.
Combine the C code into one file and compile it with clang kernel.c -o kernel.o
. This generates the last piece you need to link it.
You may now link your kernel together by running clang -T osLinker.ld -o myos.bin -ffreestanding -O2 -nostdlib bootloader.o kernel.o
.
With a very basic kernel now compiled, you may flash it onto a USB stick and run it that way, or you can run it in a virtual machine like QEMU or VirtualBox.
Resources
As a disclaimer, some of the code was borrowed from a couple of different sources:
You can follow along with the development of this series in my GitHub repository.