After IsmenaOS’s ambition — C kernel, Swift runtime, custom libc — I went the other direction. AstraeaOS is 17 files of pure assembly. No C, no libc, no cross-compiler, no toolchain repository. Just NASM and the CPU.
17 Files
The entire OS:
astraeaos/
boot/
boot.asm — Stage 1 bootloader (512 bytes, fits in MBR)
stage2.asm — Stage 2 loader (reads kernel from disk)
kernel/
kernel.asm — Kernel entry point
gdt.asm — Global Descriptor Table
idt.asm — Interrupt Descriptor Table
isr.asm — Interrupt Service Routines
irq.asm — Hardware interrupt handlers
pic.asm — Programmable Interrupt Controller setup
keyboard.asm — PS/2 keyboard driver
screen.asm — VGA text mode output
memory.asm — Basic memory management
string.asm — String utilities
ports.asm — I/O port access
link.ld — Linker script
Makefile — Build (nasm + ld)
run.sh — QEMU boot script
README.md KairosOS needed 500+ files for a kernel that did roughly the same things. The difference: C requires a cross-compiler, a libc, a startup runtime (crt0), header files, a build system that manages compilation units, and a linker script that accounts for C’s memory model. Assembly requires an assembler and a linker. That’s it.
The Boot Sequence
The assembly kernel made the boot sequence completely transparent. In C, the boot process is hidden behind your bootloader and startup code — by the time kernel_main is called, protected mode is set up, the GDT is loaded, and the stack exists. You don’t see how you got there.
In assembly, you write every instruction of the journey:
Stage 1 (512 bytes, loaded by BIOS):
- BIOS loads the first sector (512 bytes) to
0x7C00 - Set up a minimal stack
- Load Stage 2 from disk using BIOS interrupt
int 0x13 - Jump to Stage 2
Stage 2 (loaded by Stage 1):
- Enable A20 line (access memory above 1MB)
- Load the GDT — defines memory segments for protected mode
- Switch to protected mode: set PE bit in CR0, far jump to flush the pipeline
- Load the kernel from disk
- Jump to kernel entry point
Kernel entry:
- Set up the IDT — interrupt handlers for exceptions, hardware interrupts, syscalls
- Initialize the PIC — configure hardware interrupt routing
- Enable keyboard IRQ
- Print a welcome message via direct VGA memory writes
- Enter the main loop — handle interrupts, process keyboard input
Every step is an instruction I wrote. Nothing is implicit. When the CPU switches to protected mode, I can point to the exact mov cr0, eax and jmp 0x08:protected_mode_start that does it. When a key is pressed, I can trace the signal from the PIC through the IRQ handler to the keyboard driver to the character on screen.
What 17 Files Teaches That 500 Didn’t
The minimal viable kernel is smaller than you think. You need: a bootloader that switches to protected mode, a GDT, an IDT with at least exception handlers, a way to write to the screen, and an idle loop. Everything else — memory management, process scheduling, filesystem, networking — is optional. KairosOS’s 500 files included a build system, a libc, userspace stubs, and documentation infrastructure. The kernel itself was maybe 30 files. AstraeaOS’s 17 files proved that the minimum is even smaller.
Assembly is honest. There’s no compiler optimizing your code, no linker resolving symbols, no runtime initializing state. What you write is what runs. If the GDT is wrong, the CPU faults immediately. If the IDT is wrong, the first interrupt crashes the system. There’s no abstraction to blame — every bug is your bug.
C’s overhead is real but justified. After writing a keyboard driver in assembly — tracking scan codes, handling shift states, managing a key buffer — I understood why C exists. The assembly version is maybe 200 lines for a basic keyboard handler. The C version would be 50 lines and more readable. The assembly taught me what those 50 lines of C actually compile to. That’s the value: not “assembly is better” but “now I know what C does.”
The Comparison
What KairosOS needed 500+ files for, AstraeaOS achieves in 17. But “achieves” is generous — AstraeaOS can print text and handle keyboard input. KairosOS had a libc, a (basic) syscall interface, and the architecture for userspace programs. The 17-file kernel is complete in the sense that it boots and runs, but it’s a foundation, not a system.
The lesson isn’t that assembly is sufficient. It’s that stripping away every layer of abstraction reveals what the hardware actually needs from you — and it’s less than you’d think. The GDT needs to exist. The IDT needs to handle faults. The screen needs bytes written to 0xB8000. The keyboard needs IRQ1 handled. Everything else is choice.
This is Part 6 of 8 in the OS Kernel Museum series.