Most of a kernel is code your program never runs
A recent Linux kernel is past 36 million lines of code, and it grows by about nine commits an hour. All of that exists so one kernel can boot on thousands of different devices and support every feature anyone might want. But your server doesn’t want every feature. Most real systems (a cloud VM, a phone, an embedded controller) use only about 5 to 10% of the kernel. The other 90% still compiles in, still loads, and still runs with the highest privileges on the machine.
That last part is the problem. Unused code isn’t free. It’s reachable attack surface: bugs you’ll never benefit from, sitting in the most trusted layer of the system, waiting for someone to find them. The kernel is the thing everything else stands on, and we already know it’s full of bugs. Trimming it should be a reflex. In practice it almost never happens, mostly because doing it by hand is miserable.
So a few of us have tried to make it less miserable.
One kernel per app
The first idea was MultiK: stop shipping one fat kernel for everything and give each application its own, carrying only the code that app actually uses. No rewriting the program, no virtualization tax. It runs natively, and the swap is invisible to the app on top. On an Apache workload, MultiK stripped out about 94% of the kernel’s code and, with it, 19 of 23 known vulnerabilities, while adding almost no runtime overhead.
Cutting 94% of something for free sounds too good, and the catch is the question underneath it: which code does this app actually need? Answering that precisely is hard.
Doing it through the kernel’s own config
Linux already has a knob for turning features on and off: its configuration system, kconfig, with more than 30,000 options. In Pursuit of Lean OS Kernels (ACSAC 2025) works through that machinery two ways.
TRACIE watches what a workload exercises at runtime and maps those traces back to the right config options, more precisely than earlier tracing tools. That buys about a 22% size reduction.
DICE skips tracing entirely. It treats the thousands of options as a dependency graph and prunes it directly, which reaches further: roughly 36% smaller, and ten more known CVEs gone. (artifacts)
Where it hits a wall
The result I found most interesting wasn’t a number. Searching a space of 30,000-plus options sounds hopeless, but it turns out to be tractable. The real ceiling is kconfig itself. It’s coarse-grained, so one option can drag in far more than you wanted, and its dependencies are rigid, so past a point there’s code you simply cannot switch off no matter how clever the tool.