Kernel Architectures

You now know the doorway between user and kernel — the system-call interface. But that raises a design question worth a whole lesson: how much stuff should live on the privileged side of that door? The scheduler, obviously. Virtual memory, yes. But the entire ext4 file system, the TCP/IP stack, every Wi-Fi and GPU driver, the sound mixer? A modern Linux kernel is roughly 30 million lines of code, and the overwhelming majority of it is device drivers — all running in ring 0, where a single null-pointer dereference can take down the machine.

The answer to "how much goes in the kernel?" is the deepest architectural fork in operating systems. Push everything in and you get a monolithic kernel: fast, because subsystems call each other as ordinary functions, but a single trust domain where any bug is fatal. Push almost everything out into isolated user-space servers and you get a microkernel: robust and verifiable, but paying a tax every time those servers must talk. This lesson maps the whole spectrum, and the trade-off that runs through it: performance versus isolation.

The line, and what it costs to move it

Every OS draws a line: on one side, code that runs privileged and shares one address space; on the other, code that is isolated and must trap or message to get anything done. Moving a subsystem across that line changes two things in opposite directions:

That is the entire debate in one sentence: isolation is bought with IPC, and IPC is not free. The whole zoo of kernel architectures — monolithic, micro, hybrid, exo, uni — is different answers to where to stand on that line, given a workload and a threat model.

Two ways to place the same components

Here are the same jobs — scheduling, memory, file systems, drivers — placed two different ways. On the left, all of them sit below the privilege line in one kernel. On the right, only three primitives stay privileged; the file system, network stack, and drivers become ordinary user-space servers, reached by messages.

The left diagram has one big trusted box and few crossings; the right has many small isolated boxes and many crossings. Neither is "correct" — they optimise different things. A phone baseband or a pacemaker wants the right-hand picture (a bug must not be catastrophic); a database server maxing out NVMe bandwidth wants the left.

The full spectrum

Real systems spread across five recognisable points. Read the table as a line from "everything in the kernel" to "almost nothing in the kernel", then two exotic points that rethink the abstraction entirely.

ArchitectureReal systemsIn the kernelIsolationCost driver
MonolithicLinux, FreeBSD, classic Unixeverything: sched, VM, FS, net, driverslow — one trust domaincheap: in-kernel function calls
MicrokernelseL4, Mach, MINIX 3, QNXIPC, threads, address spaces onlyhigh — each server isolatedthe IPC / microkernel tax
HybridWindows NT, XNU (macOS)a Mach-ish core + subsystems kept in-kernel for speedmediumpragmatic compromise
Exokernel / library OSMIT Exokernel, Nemesis, Barrelfishonly secure multiplexing of raw hardwareapp-definedOS abstractions linked into the app (libOS)
UnikernelMirageOS, IncludeOS, Unikraftno user/kernel split — one app + libOS in one address spacevia the VM/hypervisor boundarytiny, single-purpose image

Notice the exokernel and unikernel don't sit on the monolithic⇄micro line at all — they change the question. An exokernel says "don't give me abstractions, give me safe access to the raw disk and I'll build my own file system as a library." A unikernel says "there is only one application, so why have a protection boundary inside the image at all?" — and relies on the hypervisor for isolation instead.

Counting the tax

Make the trade-off concrete. Consider one operation — reading a file — which must touch the file system and a block-device driver. In a monolithic kernel that is one user↔kernel crossing; the FS calls the driver as a function. In a microkernel, the application messages the FS server, which messages the driver server, and each message is a full round trip. The simulation tallies the crossings for both designs.

// How many privilege/context crossings does ONE file read cost, monolithic vs microkernel? // Model: a crossing is a user<->kernel or client<->server round trip. Say ~350 cycles each. const CYCLES_PER_CROSSING = 350; // Monolithic: app traps into the kernel once; FS and driver are in-kernel function calls (free). function monolithicRead(): number { const crossings = 1; // the read() syscall itself console.log(`monolithic: ${crossings} crossing (FS -> driver are in-kernel calls)`); return crossings; } // Microkernel: app -> FS server -> driver server, each an IPC round trip through the kernel. function microkernelRead(): number { const hops = ["app -> FS server", "FS server -> driver server", "driver -> FS reply", "FS -> app reply"]; hops.forEach((h) => console.log(` IPC: ${h}`)); console.log(`microkernel: ${hops.length} crossings`); return hops.length; } const mono = monolithicRead(); const micro = microkernelRead(); console.log(`\ntax = ${(micro - mono) * CYCLES_PER_CROSSING} extra cycles per read`); console.log(`microkernel does ${(micro / mono).toFixed(1)}x the crossings`); console.log("=> this is exactly the cost L4-family IPC had to attack to be viable");

The design law

In January 1992, on the Usenet group \texttt{comp.os.minix}, Andrew Tanenbaum — author of the MINIX microkernel and a leading OS textbook — posted a message titled "LINUX is obsolete." His argument was principled: monolithic kernels were a 1970s design, microkernels were the future, and tying Linux to the x86 was a mistake. Linus Torvalds, then a 22-year-old student, fired back that a microkernel's elegance was worth nothing if it was slow, and that a working, fast system beat a beautiful, sluggish one. History gave a split verdict: Linux's monolithic design conquered servers, phones, and supercomputers precisely because of its performance and pragmatism — and Tanenbaum was vindicated where correctness is non-negotiable, as seL4 (a microkernel) became the first OS kernel with a machine-checked proof of functional correctness. Both men were right about their own axis of the trade-off.

Two myths to kill. First, monolithic describes only that the components share one address space and privilege level — not that they are a tangled mess. Linux is intensely modular internally (loadable modules, the VFS layer, clean subsystem interfaces); it is monolithic because those modules run in the kernel, not because they are spaghetti. Second, calling Windows NT or macOS's XNU a hybrid can sound like "the best of both worlds", but it really means "a microkernel-derived design that pulled performance-critical services (file systems, drivers, graphics) back into the kernel address space to avoid the IPC tax." A hybrid buys speed by giving up the microkernel's isolation for those subsystems — a compromise, not a free lunch.