Part V: Systems Programming
Assembly Meets the Operating System
There is a moment in every systems programmer's education when the abstraction dissolves. You have been writing programs that call printf, that allocate memory with malloc, that open files with fopen — all without thinking too hard about what happens underneath. Then you write your first raw syscall instruction, watch the CPU ring level drop from 3 to 0, and you understand: the operating system is not magic. It is code. Code that runs on the same hardware you have been programming all along.
Part V is about that moment, extended across six chapters.
The operating system sits at the intersection of hardware and software. It manages hardware resources — CPU time, physical memory, I/O devices — and exposes them to user programs through controlled interfaces. The primary interface is the system call: a supervised entry into kernel mode that lets user code request privileged operations. Chapter 25 examines this interface in exhaustive detail, from the syscall instruction's mechanics to building a minimal shell with nothing but raw system calls and NASM.
But system calls are only one way hardware talks to software. The other direction — hardware talking to the CPU — is the subject of Chapter 26. Interrupts and exceptions are how keyboards announce keystrokes, timers fire, and page faults get reported. Setting up the Interrupt Descriptor Table (IDT), writing interrupt handlers, and understanding ring transitions are the skills that separate systems programmers from application programmers.
Memory management, covered in Chapter 27, is the abstraction that makes modern operating systems possible. Every pointer in a user program is a lie — a virtual address that the CPU translates, on every memory access, through a four-level page table walk. Understanding that walk, and understanding how the kernel manages it, explains why buffer overflows are exploitable, why ASLR matters, why mmap is fast, and why your 64-bit process thinks it has 128 terabytes of address space.
Chapter 28 descends below the operating system entirely. Bare metal programming means starting from the moment the CPU exits reset: 16-bit real mode at address 0xFFFF0, a 512-byte bootloader that has to bootstrap everything else, and the CPU mode transitions that take you from 1MB of real-mode address space to the full 64-bit long mode your kernel will live in. This chapter contains a complete, annotated bootloader that you can assemble, burn to an image, and boot in QEMU.
Chapter 29 covers device I/O — how software talks to hardware. Two paradigms dominate: port-mapped I/O, using the IN and OUT instructions, and memory-mapped I/O, where device registers appear as ordinary memory addresses. You will program real devices: the PIT timer, the UART serial port, the PIC interrupt controller. These skills are directly applicable to embedded systems work as well as OS development.
Chapter 30 examines what happens when multiple CPUs run simultaneously. Concurrency at the hardware level is fundamentally different from concurrency as taught in operating systems courses. The hardware does not guarantee that memory writes become visible to other processors in the order you wrote them. The LOCK prefix, CMPXCHG, and memory fences are the tools you use to impose order on a system that, by default, trades correctness for performance.
The MinOS Kernel Project
Running through Part V is the MinOS kernel project — a progressive OS kernel built from scratch. By the end of Part V, MinOS will:
- Boot from a BIOS-style 512-byte MBR bootloader (Chapter 28)
- Transition through real mode → protected mode → long mode
- Handle interrupts via a working IDT (Chapter 26)
- Process keyboard input through an IRQ1 handler (Chapter 26)
- Keep time via the PIT timer (Chapter 29)
- Manage physical memory with a bitmap allocator (Chapter 27)
- Accept system calls through a syscall handler (Chapter 25)
- Run on multiple CPUs with spinlock synchronization (Chapter 30)
MinOS is not a production kernel. It is a learning artifact — every piece exists to demonstrate a concept, and every concept connects to production systems you will work with in the real world. Linux, FreeBSD, and seL4 all solve the same problems. MinOS just solves them in the fewest lines possible, with every decision visible.
Why Assembly for Systems Programming?
Much systems programming today is done in C, Rust, or even Go. Assembly remains essential for the parts these languages cannot express: the syscall instruction itself, the IDT entry format, the LGDT and LIDT privileged instructions, the precise bit layout of a page table entry, the LOCK CMPXCHG that implements every mutex in the world. These constructs exist below the abstraction floor of every high-level language. To implement them, you must speak the CPU's language directly.
You already know the language. Part V is where you use it to build something real.