← All chapters
Chapter 4· 16 min read · illustrated

OS Structures & Architectures

How the kernel is organized inside — and the trade-offs behind every choice

By now you know what a kernel does: it manages the CPU, memory, and devices, and it guards the boundary between user space and hardware. But a kernel is not a single tidy function — it is millions of lines of code covering the scheduler, the memory manager, dozens of file systems, and hundreds of device drivers. All of that has to live somewhere and talk to itself somehow. How you arrange those pieces is a genuine engineering decision, and different operating systems answer it in strikingly different ways.

This chapter is about internal architecture: the shapes a kernel can take, and why. We will look at monolithic kernels, layered designs, microkernels, and the pragmatic hybrids most real systems ship today. Along the way one theme keeps returning — you are constantly trading raw speed against reliability, security, and how easy the thing is to change without breaking it.

If you build software for a living, this is not abstract trivia. The same tensions that shape a kernel — keep everything in one fast process, or split it into isolated services that message each other — are the exact tensions you face when you decide between a monolith and microservices. The kernel designers just had to answer these questions decades earlier, with the whole machine on the line.

01

The core problem: how do you organize a kernel?

A kernel has to do a great many different jobs, and every one of them is critical. It schedules threads onto CPUs, hands out and reclaims memory, drives the disk and the network card, implements the file systems, and answers every system call your programs make. None of these can simply be left out, and all of them need to run with hardware privilege. The question is not what goes in a kernel, but how those parts should be arranged relative to one another.

That arrangement matters because the pieces constantly call each other. When you write a file, the file system asks the memory manager for buffers and hands data to a disk driver. If those components sit together in one address space, the calls between them are as cheap as an ordinary function call. If they are separated into isolated protected units, every such interaction becomes a guarded message that crosses a boundary — safer, but slower. The whole chapter lives in that one trade-off.

Kernel structure
How the kernel’s internal components are grouped, isolated, and allowed to talk to each other.
Address space
A protected memory region; code in one space cannot freely touch another’s memory.
Trusted computing base
The code running with full privilege that you must trust completely — you want it small.

The tension: Every kernel design is a bet on how to balance three things that pull against each other: performance, reliability, and maintainability.

Tap to enlarge
02

Mechanism vs policy

Before comparing kernel shapes, learn the design principle that underlies all of them: separate mechanism from policy. A mechanism is the machinery that makes something possible — how it is done. A policy is the decision about what to do and when. The classic rule of thumb is that mechanisms should be flexible and general, while policies should be easy to change without rewriting the machinery underneath.

Scheduling is the cleanest example. The mechanism is everything needed to actually switch the CPU from one thread to another — saving registers, setting a timer, restoring the next thread’s state. The policy is the rule that decides which thread to run next — round-robin, priority-based, or something fairer. A well-built kernel lets you swap the policy (Linux, for instance, offers several scheduling classes) while the context-switch mechanism stays untouched.

Mechanism
The reusable machinery that performs an action — the “how”. Ideally general and stable.
Policy
The decision about what to do and when — the “what/when”. Ideally swappable.
Separation
Keeping the two apart so policies can change without disturbing the mechanism.

For engineers: This is the same instinct behind a clean API: expose a general mechanism (a payment gateway call) and let callers plug in policy (which provider, what retry rule). You have been applying this principle without naming it.

Tap to enlarge
03

Monolithic kernels

The oldest and most common design is the monolithic kernel: put essentially everything — scheduler, memory manager, file systems, network stack, and device drivers — into one large program running in a single privileged address space. Because all the pieces share that space, they call each other directly, with nothing but a function call between the file system and the disk driver. That directness makes monolithic kernels very fast.

The cost is isolation, or the lack of it. Since everything runs with full privilege in one space, a bug anywhere can corrupt anything. A flaw in an obscure device driver can overwrite the scheduler’s data and bring the entire machine down, because there is no wall between them. The codebase is also large and tightly interwoven, which historically made monolithic kernels harder to reason about and extend.

Linux is the headline example, and it is important to be precise: Linux is a monolithic kernel, but a modular one. All the core subsystems live together in kernel space for speed, yet drivers and features can be compiled as loadable modules and slotted in at runtime. That modularity tames the maintainability problem without giving up the performance of the shared address space — we will come back to modules shortly.

Single address space
All kernel components share one protected memory region and call each other directly.
Fast path
Cross-component calls are ordinary function calls — no boundary crossing, no message.
Blast radius
A bug in any component can corrupt the whole kernel; there is no internal wall.

Real systems: Linux and the classic Unix kernels are monolithic. It is the design that runs most servers, phones, and cloud workloads on Earth.

Tap to enlarge
04

Layered systems

A layered system tackles the tangle of a big kernel by imposing strict order: organize the OS into a stack of layers, where each layer may only use the services of the layer directly below it and offers services to the layer directly above. Layer 0 is the hardware; the top layer is the user interface. This is the classic textbook structure, and its appeal is real — each layer can be built and understood using only what is beneath it, so you can reason about and debug the system one level at a time.

The difficulty is that real operating systems do not decompose cleanly into a neat pile of layers. Deciding what belongs in which layer is genuinely hard, because subsystems often need each other in ways that cross layer boundaries — the memory manager may need the disk driver to page data out, while the disk driver may need the memory manager for its buffers. Strict layering also adds cost: a single request may have to pass down through many layers, each adding a little overhead, which can make the system slow compared with a design that just calls across directly.

Layer
A group of functionality that uses only the layer below and serves only the layer above.
Clean dependency
Each layer’s correctness depends only on the layers beneath it — easy to reason about.
Rigidity
Real subsystems have circular needs that a strict layer stack cannot express cleanly.

Where you see it: Pure layering is rare in whole kernels, but the idea thrives in the networking stack (the OSI/TCP-IP layers) and in how you structure application software.

Tap to enlarge
05

Microkernels

A microkernel takes the opposite bet from a monolith: keep the kernel as small as possible. Only the truly essential functions stay in privileged kernel space — inter-process communication (IPC), basic scheduling, and low-level memory management. Everything else that a monolith would keep inside the kernel — device drivers, file systems, the network stack — is pushed out to run as ordinary user-space processes called servers.

Those servers cannot call each other directly anymore, because each lives in its own address space. Instead they communicate by message passing, relayed through the microkernel. When an application wants a file, it sends a message to the file-system server, which may send a message to the disk-driver server, and replies flow back the same way. The kernel’s main job shrinks to being a fast, secure post office for these messages.

The payoff is robustness and security. If a device driver crashes, only its server dies — the kernel can often just restart it while the rest of the system keeps running, because the faulty driver never had the privilege to corrupt anything else. The trusted computing base is tiny, which is why microkernels dominate where correctness is non-negotiable. The cost is IPC overhead: all that message passing across address-space boundaries is slower than a monolith’s direct calls, and taming that overhead has been the central challenge of microkernel research.

Microkernel
A minimal kernel holding only IPC, scheduling, and basic memory management.
User-space server
A driver or file system running as an unprivileged process outside the kernel.
Message passing
The IPC by which servers and apps request work from one another through the kernel.
Fault isolation
A crashing server can be restarted without taking down the kernel or other servers.

Real systems: MINIX (built to teach these ideas), QNX (real-time, in cars and medical devices), and seL4 (formally proven correct) are microkernels. seL4 is used where a bug is unacceptable.

Tap to enlarge
06

Modules & hybrid kernels

Most real systems refuse to pick a pure extreme, and instead land in a pragmatic middle. The first tool for that is the loadable kernel module: a chunk of kernel code — usually a driver or a file system — that can be loaded into and unloaded from a running monolithic kernel on demand. Linux uses this heavily. You get the speed of a shared address space, but you no longer ship one giant fixed binary; you load exactly the drivers your hardware needs, and update them without rebuilding the whole kernel.

The second is the hybrid kernel, which borrows the vocabulary of microkernels while keeping performance-critical services inside kernel space. A hybrid design is organized into components that could, in principle, be separate servers — but the parts that would suffer most from IPC overhead, like drivers and file systems, are kept in the kernel and called directly. It is a deliberate compromise: some of the modularity and structure of a microkernel, most of the speed of a monolith.

This is what you are almost certainly running. Windows NT (the basis of every modern Windows) is a hybrid kernel. Apple’s XNU, at the heart of macOS and iOS, famously combines a Mach-derived microkernel with a monolithic BSD layer in a single kernel space. These systems chose the middle path because, in practice, pure purity on either end costs more than it is worth.

Loadable module
Kernel code loaded/unloaded at runtime; adds features without rebuilding the kernel.
Hybrid kernel
A kernel structured with microkernel ideas but keeping key services in kernel space for speed.
Pragmatic compromise
Trading a little of one extreme’s purity to capture most of the other’s benefit.

Real systems: Windows NT is a hybrid; macOS/iOS run on XNU (Mach + BSD). Linux is a modular monolith. The middle ground is where the mainstream lives.

Tap to enlarge
07

Comparing the approaches & why it matters to you

Line the three main approaches up and the trade-off becomes concrete. A monolithic kernel is fastest, because components call each other directly, but least robust, because a single bug has the run of the whole kernel. A microkernel is most robust and most secure, because faults are isolated in user space and the trusted core is tiny, but pays for it with IPC overhead. A hybrid deliberately sits in between, keeping the hot paths in-kernel for speed while borrowing structure from the microkernel world.

Monolithic — speed
Highest: direct in-kernel calls, no boundary crossings. Robustness lower; example: Linux.
Microkernel — robustness
Highest: isolated servers, tiny trusted base. Speed lower from IPC; examples: QNX, seL4.
Hybrid — balance
Near-monolith speed with some modularity; examples: Windows NT, macOS/iOS (XNU).
Complexity
Monoliths are simple to call but hard to isolate; microkernels invert that.

Here is where it touches you directly. On a monolithic system, a faulty graphics or storage driver runs with full kernel privilege, so a bad enough bug can freeze or crash the entire machine — the Windows blue screen and the Linux kernel panic are exactly this. On a microkernel system like QNX, that same driver runs as an isolated user-space server, so when it dies the OS can restart it and the machine stays up. That is precisely why microkernels are chosen for cars, aircraft, and medical devices, where a crash is not a mere inconvenience.

Notice how closely this mirrors your own architectural choices. A monolithic kernel is a monolith app: fast internal calls, simple to build, but one bad module can take the whole thing down. A microkernel is microservices: isolated processes talking by messages, resilient to a single failure, but paying a real cost in the communication between them. The kernel world has been living this debate for forty years, and the industry-wide answer — hybrids, modular monoliths, sensible middles — is the same lesson your own systems keep teaching you.

Looking ahead: We have seen how the kernel is shaped from the outside in. Next we go inside one of its central jobs — the process: how a program becomes a running process, and how the OS keeps track of it.

Tap to enlarge