Running a full OS kernel

Week 10

Weeks 5 to 8 were kernel services without a kernel

Descriptors without a descriptor table. Processes made of workers. A network interface made of messages. A filesystem you wrote.

Every one of them was an approximation of something an operating system already does correctly, and every one of them failed in a way that traced back to the approximation.

This week concedes the point — and immediately splits in two.

Two sentences that sound identical

Emulate a machine. The kernel is unmodified, because it does not know anything has changed. You are implementing hardware.

Port the kernel. There is no machine at all. You are adding WebAssembly as a target architecture to an operating system.

These are not variations on one approach. They share no code, fail for different reasons, and the second one is a compiler project.

Emulating a machine means writing devices

An interrupt controller. A programmable interval timer. A real-time clock. A disk controller. A network card. A display adapter with its vendor extensions. A sound card, because something will probe for one.

All of it in software, all of it faithful enough that a driver written in 1998 is satisfied.

It boots operating systems from the nineties without their cooperation, and it is where the browser’s oldest party trick comes from.

How the fast ones are fast

The mature x86 emulator interprets first, and measures hotness per 4 KB page. When a page is hot it compiles that page — plus the pages reachable from it — into a single WebAssembly function, with a jump table at the top to enter at the right offset.

Guest control flow becomes structured WebAssembly control flow, which is a real problem with a real algorithm behind it, because WebAssembly has no goto.

And what it cannot do

Multicore is explicitly missing. So is 64-bit.

Those are not roadmap items. A single-threaded emulator with a software address translator has nowhere to put a second core, and adding one means modelling memory ordering between guest cores on top of a host that already has its own.

The oldest technique in the course is also the one with the hardest ceiling.

Porting the kernel instead

Add WebAssembly as a Linux architecture. No devices, no emulated CPU — the kernel is compiled to the target and runs there.

Which forces one enormous decision immediately: WebAssembly has no memory management unit.

So the kernel is built without one, and every userspace program must be position-independent, because nothing will be relocating anything for it.

What NOMMU costs you

No fork in the sense you mean it. No copy-on-write. No demand paging. No mprotect. Address space layout is a shared problem rather than a private one.

That is a different operating system with a different contract, and a great deal of existing software quietly assumes the contract it does not have.

It is also the clearest demonstration available that “run a kernel” and “emulate a machine” are not the same sentence.

What either one buys

Real processes. A real fork. A real filesystem with real semantics. A real network stack, with a real routing table.

Everything weeks 5 to 8 spent themselves approximating, done by the code that was written to do it.

One RISC-V system boots Linux from a snapshot in about a third of a second — because the expensive part of booting is deterministic, so you do it once at build time and ship the resulting memory image.

Snapshot boot is the good idea in this week

Boot is a pure function of the kernel, the initramfs and the configuration. Run it once, freeze the machine state, and every subsequent start is a memcpy.

It converts a start-up cost into a download cost, which you can compress, cache and version.

The trade is that the snapshot is large, and that anything which must differ per session — clocks, entropy, identity — has to be re-derived after resuming, carefully, or you have shipped every user the same random seed.

And either way, you pay — because this is not virtualisation

A hypervisorAn emulator in a tab
guest instructions run on the real CPUevery instruction is dispatched by software
guest registers live in real registersguest registers live in linear memory
the hardware page walker does translationyou do it, per access, in software
the real TLB caches ityou write your own TLB and hope it fits
only privileged operations trap outnothing is privileged, so nothing traps

A page in a browser cannot install a page table.

Which is the whole cost, in one sentence

Every single guest memory access is a software lookup followed by a bounds-checked WebAssembly access. Not the slow paths. All of them.

And with no mprotect, self-modifying code cannot be caught by a fault — it has to be caught by tracking dirty pages yourself, which is another check on every write.

That is the whole cost, and it is structural. No amount of optimisation removes a layer that the hardware was doing for free.

The side effect

Once there is a kernel, graphics becomes tractable in a way it was not in any previous week.

Not because drawing got easier — because there is now something for a display driver to bind to. You can implement a graphics device and the kernel’s mode-setting interface, and an unmodified display server will drive it.

That is next week, and it is the clearest case in the course of an approach deciding something on your behalf.

In the session

Nobody boots a kernel for their own target in an hour, so you are given a machine that already boots, and you measure a kernel rather than a program.

Time to a shell prompt. Time to a second process. What it costs to open a file. What fork costs, and whether it is real.

Against the same four numbers on your own machine. The ratio is the finding, and it will not be the same ratio four times.