ABHIJAT
← Back to Writing

Systems

Building a tiny operating system to understand computers better

Notes from writing ASH OS in C and x86-64 assembly, and what it taught me about the layers above the kernel.

Abhijat2026-0410 min read7 views

Last updated September 15, 2026

Reading how an operating system works and writing one that boots are not the same kind of understanding. I'd read plenty of OS theory before starting ASH OS — memory management, scheduling, file systems — and none of it prepared me for how much sits underneath "the kernel starts running" that a textbook chapter summarizes in a sentence.

What actually happens between power-on and a running kernel

Theory presents boot as a mostly-solved handoff: firmware finds a bootloader, the bootloader loads a kernel, the kernel takes over. Writing a bootloader in x86-64 assembly makes every step in that handoff a decision you have to make yourself, with no framework doing it for you: switching the CPU out of legacy real mode into protected mode and then long mode, setting up initial page tables before any C code can safely run, establishing a stack the kernel can actually use. None of this is conceptually hard in isolation. All of it has to happen in the right order, with no runtime yet to catch a mistake — a wrong step here doesn't throw an exception, it hangs the machine with no error message at all, and the only debugging tool is knowing exactly what should have happened at each stage and checking it by hand.

Optimizing a boot sequence you fully control

With the full boot path under direct control, optimizing startup time stops being about tuning parameters in someone else's system and starts being about deciding, explicitly, what has to happen before the kernel can hand off to user-visible code, and what can be deferred until after. Every step in a hand-written boot sequence is a step you chose to include, which means every step is also one you can question — is this initialization actually needed before the kernel starts, or was it just done early because that's where it seemed to belong. That's a different kind of optimization than tuning a stack you didn't design; it's closer to deciding what belongs in a fast path at all.

Access control at the kernel, not layered on top

Most software enforces "who can access what" in user space — an application layer checking permissions before allowing a read. ASH OS's file access policies are enforced at the kernel level instead: the kernel itself is the thing deciding whether a given access is allowed, before any user-space code gets a chance to be wrong about it, bypassed, or skipped by a buggy caller. This is a meaningfully different security posture — not "we check permissions carefully in the application," but "the layer everything else runs on top of is the one enforcing the rule," which means there's no user-space code path that can accidentally skip the check, because the check isn't in user space to skip.

What changed above the kernel

The most lasting effect of building ASH OS wasn't anything specific to operating systems — it was how it changed my read of every layer of software built on top of one. A lot of what looks like an arbitrary convention in application code — why a certain operation is expensive, why a certain pattern exists, why some constraint feels like it "just has to be that way" — turns out to be a direct consequence of something at the OS or hardware layer that most application code never has to think about because the OS already absorbed it. Having written that absorption layer myself, by hand, makes those constraints visible in code where they used to just look like unexplained rules. That shift in how I read every layer above the kernel has been worth more, long-term, than anything specific to systems programming that the project taught directly.

Tags

Operating SystemsCx86-64