Most people learn how operating systems work from textbooks.
I decided to just build one.
Not with GRUB. Not with UEFI abstractions. Just raw x86_64 Assembly, freestanding C, and a lot of time spent reading Intel manuals at 2am.
This is the story of AxiomX-OS — a fully custom x86_64 operating system I built from scratch, including its own 4-stage bootloader, physical and virtual memory manager, round-robin scheduler with context switching, and an interactive shell with 24 built-in commands.
Here's what I built and how.
Why build an OS?
"How does an OS actually work?" is one of those questions that deserves a real answer — not a textbook chapter, but actual code running on actual hardware.
I wanted to understand what happens between pressing the power button and running a program. Every layer of abstraction people take for granted — memory, processes, interrupts — I wanted to build myself.
So I did.
The Architecture: A Custom 4-Stage Bootloader
Most hobby OS projects start with GRUB. I didn't want that. GRUB hides too much — I wanted to understand the full boot sequence from the very first instruction the CPU executes.
AxiomX boots through 4 stages, all written in pure x86 Assembly:
Stage 0 → Stage 1 → Stage 2 → Stage 3 → Kernel
BIOS Real Protected Long 64-bit C
MBR Mode Mode Mode Entry
Stage 0 (MBR): The BIOS loads 512 bytes from sector 0 into memory at 0x7C00. That's your entire budget for the first stage — 512 bytes to set up the stack, load the next stage, and hand off control.
Stage 1 (Real Mode): Running in 16-bit real mode with access to BIOS interrupts. I use this stage to call the E820 BIOS interrupt to build a memory map of the system — which memory regions are usable, reserved, or ACPI-related. This data is critical for the physical memory manager later.
Stage 2 (Protected Mode): Sets up a Global Descriptor Table (GDT), enables the A20 line, and jumps into 32-bit protected mode. No more BIOS calls from here.
Stage 3 (Long Mode): Sets up page tables for identity mapping, enables PAE and long mode via MSRs, and finally jumps into 64-bit mode before handing off to the C kernel entry point.
Writing this from scratch taught me more about x86 architecture than any textbook ever could.
Memory Management: PMM + VMM + Heap
Once the kernel is running, the first thing it needs is memory management.
Physical Memory Manager (PMM)
The PMM tracks which physical pages (4KB each) are free or used, using a bitmap allocator. Each bit represents one 4KB page. Allocating memory is a matter of finding the first 0 bit, flipping it to 1, and returning that page's address.
Simple in concept, but getting the bitmap correctly initialized from the E820 map — respecting reserved regions, kernel regions, and usable RAM — took careful work.
[pmm] Initialised: 32736 total pages, 31967 free pages
Virtual Memory Manager (VMM)
The VMM implements x86_64 paging using a PML4 page table structure with 2MB huge pages. Every virtual address goes through a 4-level page table walk before reaching physical memory.
Setting up identity mapping for the first 128MB of memory means the kernel's virtual addresses match its physical addresses — which simplifies early kernel development enormously.
[vmm] PML4 at 0x4000, 128MB identity-mapped
Kernel Heap
On top of the VMM sits a simple dynamic heap allocator — the kernel equivalent of malloc and free. Without this, the kernel can't dynamically allocate data structures at runtime.
Interrupts: IDT + PIT
The Interrupt Descriptor Table (IDT) tells the CPU what to do when an interrupt fires — whether it's a hardware timer, a keyboard press, or a CPU exception like a page fault.
Setting up the IDT involves writing 256 entries, each pointing to an ISR (Interrupt Service Routine). The tricky part is that the CPU pushes different amounts of data onto the stack for different exceptions, so you need stub handlers in Assembly to normalize the stack before jumping into C.
The PIT (Programmable Interval Timer) fires at 100 Hz — 100 times per second. Every tick is used by the scheduler to decide whether to switch tasks.
[pit] Initialised at 100 Hz (divisor=11931)
Scheduler: Round-Robin with Context Switching
The scheduler is probably the most satisfying part to get working.
AxiomX uses a round-robin scheduler — each task gets a fixed quantum (10 ticks), and when the quantum expires, the PIT fires an interrupt and the scheduler picks the next task in the queue.
The hard part is context switching — saving the current task's CPU state (all registers, stack pointer, instruction pointer) and restoring the next task's state, all in Assembly, while making it transparent to both tasks.
[sched] Initialised. Idle PID=0, quantum=10 ticks
Watching three stress workers run simultaneously for the first time — each completing exactly 50,000 iterations and confirming round-robin — was genuinely one of the best moments of this project:
AxiomX-OS> stress
worker0 : 50000 iters
worker1 : 50000 iters
worker2 : 50000 iters
Round-robin confirmed
The Shell: 24 Built-in Commands
AxiomX drops into an interactive shell on boot, with 24 built-in commands including:
-
mem— live memory stats and usage bar -
ps— list processes and states -
schedviz— visual scheduler state and timing -
hexdump <addr>— hex dump memory at any address -
virt2phys <addr>— walk page tables and translate virtual → physical address -
regs— dump all CPU registers -
stack— dump current stack + call frame chain -
trace on|off— live kernel event tracing -
cpuinfo— CPU info via CPUID -
vmmap— virtual memory layout
TAB autocomplete and UP/DOWN history navigation are also implemented.
What I Learned
Building an OS from scratch teaches you things that no amount of high-level programming can:
-
Nothing is magic. Every abstraction you use daily —
malloc, processes, file I/O — is code someone wrote. Once you write it yourself, you stop taking it for granted. - The CPU is deeply weird. Real mode, protected mode, long mode, segment registers, MSRs, the A20 line — x86 has 40 years of backwards compatibility baked in and it shows.
- Debugging without tools is humbling. No gdb, no printf (at first), no stack traces. Just a serial port, a hex dump, and careful thinking.
- The Intel manual is your best friend. Seriously. Every answer is in there.
What's Next
AxiomX-OS is still a work in progress. Planned next steps:
- [ ] PS/2 Keyboard driver
- [ ] FAT32 filesystem
- [ ] Userspace & syscall interface
- [ ] ELF loader
- [ ] Basic process isolation
Try It Yourself
The full source code is on GitHub — including all 4 bootloader stages, the kernel, and a Makefile to build and run it in QEMU:
👉 github.com/ChairSleeper/AxiomX-OS
Requirements: nasm, gcc (x86_64-elf cross-compiler), ld, qemu-system-x86_64, make.
make # build everything
make run # run in QEMU
If this project was useful or interesting to you, consider supporting my open source work:
💖 github.com/sponsors/ChairSleeper
Every bit helps me keep building. Thanks for reading!
Top comments (0)