Customising linux kernel in nixos
There has been some noise around the linux project because Linux Torvalds, its leader, has started using LLMs for writing patches.
And this is not his first loud opinion in the matter. He wants people not to complain about it:
While I think using LLMs for complex code is a negative thing as it introduces more complexity through losing context while wasting energy, I donât think I have enough stamina to switch to some BSD distribution for now.
I think this post from David Chisnall summarises the idea quite well:
Claude lacks proper context of the use of the software but spots a pattern that looks like things itâs training set has flagged as slow and so points to the slow path as a bottleneck.
The user asks for a test case and Claude provides a simple microbenchmark that triggers the slow path.
Now that itâs confirmed as slow, the user asks a âagenticâ harness to explore the design space and optimise for the benchmark. The swarm of Claude instances makes plausible changes to the code and reports back with a modified version that passes the test suite and improves the microbenchmark.
The test suite doesnât exhaustively cover all possible race conditions and so the new version may include subtle concurrency bugs. It also probably makes the common case slower and so is a regression overall.
What I can do is to reduce the surface of slop by using less features in the kernel, so I decided to go ahead with nix configuration.
Custom kernel with nixos
I followed this wiki entry:
https://nixos.wiki/wiki/Linux_kernel#Custom_configuration
and tried with a few parameters using the boot.kernelPatches
option.
The first thing to note is that the example uses the string syntax
boot.kernelPatches = [ {
name = "crashdump-config";
patch = null;
extraConfig = ''
CRASH_DUMP y
''
}];
However later in the documentation they recommend to use
structuredExtraConfig which looks like:
boot.kernelPatches = [ {
name = "simplified-config";
patch = null;
structuredExtraConfig = with lib.kernel; {
ACPI_HOTPLUG_MEMORY = lib.mkForce unset;
AMD_HFI = lib.mkForce unset;
AMD_MEM_ENCRYPT = lib.mkForce unset;
B43LEGACY = no; # don't have it
}];
They also recommend using ignoreConfigErrors to avoid error:
unused option during build. But I chose to ignore them manually
using the lib.mkForce unset that you see above.
Something else I had trouble with: I couldnât find documentation for how to set a literal. I found this in some nix definition and adapted to the list:
NR_CPUS = lib.mkForce (freeform "8"); # I have 4 cpus * 2 HT
Removing modules
As I got some options set, I decided to run and see how long would it take. Unfortunately the default for a generic distribution kernel is to have everything as a module to support as many configurations as possible. So more than 1 hour in my i3.
I decided to use lsmod to see which modules I had running in the
system, and then gunzip /proc/config.gz to see the existing
configuration. Using some vim to get both files I managed to match
most of the modules I was using against the current kernel
configuration, and change every other module to ânoâ.
My configuration.nix looked like 7000 lines of this:
structuredExtraConfig = with lib.kernel; {
...
#INET_AH=no;
#INET_ESP=no;
#INET_ESP_OFFLOAD=no;
#INET_IPCOMP=no;
#INET_XFRM_TUNNEL=no;
#INET_TUNNEL=no;
#INET_DIAG=no;
#INET_TCP_DIAG=no;
#INET_UDP_DIAG=no;
#INET_RAW_DIAG=no;
#TCP_CONG_BIC=no;
#TCP_CONG_WESTWOOD=no;
#TCP_CONG_HTCP=no;
#TCP_CONG_HSTCP=no;
#TCP_CONG_HYBLA=no;
#TCP_CONG_VEGAS=no;
#TCP_CONG_NV=no;
#TCP_CONG_SCALABLE=no;
#TCP_CONG_LP=no;
#TCP_CONG_VENO=no;
#TCP_CONG_YEAH=no;
#TCP_CONG_ILLINOIS=no;
#TCP_CONG_DCTCP=no;
#TCP_CONG_CDG=no;
#TCP_CONG_BBR=no;
...
}
Unfortunately setting all of them of no was risky because there are a few modules that I couldnât match to any config and make the system not boot.
To figure outI tried using the web, https://www.kernelconfig.io/index.html and searches but some of them where too hard to guess.
Trying local LLMs
TO solve this while experimenting with LLMs to refine my opinions and get some heat in the room, I decided to try my local ollama to ask for the module. From all the models that I can run, only qwen3.6 had some information within its weights.
I tried at first to ask to run lsmod and give me all the matches, but there is no way the model can do that at all, it loses context very quickly.
Then I ran a pydantic-ai script to get one answer every ~10 minutes:
kernel_modules = [
"af_packet", ...]
agent = Agent( # (1)
"ollama:qwen3.6:35b-a3b",
model_settings={"thinking":False},
deps_type=str,
instructions=(
"return the linux kernel configuration option that generates the module that the user provides. Return only the string that starts with CONFIG_"),
)
for kernel_module in kernel_modules:
print(kernel_module)
result = agent.run_sync(f'The module is {kernel_module}', conversation_id='new') # (3)
print(result.output)
print("--------------------")
The thing is that it matched modules that were easy. Any module that wasnât immediately obvious would not get right and give a false answer. It is considerably worse than me searching manually. It was building up ram usage too, around 300mb per question.
Instead I asked for some grep to get the module from the kernel sources. I got a few options:
- Grep Kbuild Makefiles (Most Direct)
- Grep Kconfig for
tristate - greping modname.h
- Using module markers
- Using python
kconfiglib
The thing is that 1 and 3 seemed to be the easiest and more reliable options (2 and 4 did not work for me, and I didnât try 5). Both of them I already knew and I used a faster searcher than grep anyway.
Lastly, running make menuconfig in the linux kernel source
directory and using the search inside was an even better option as
I had help and visibility of the path.
Removing modules slowly
Despite being almost certain that I have all the modules that I
need declared as modules and everything else declared as no, I am
slowly adding some of these declarations to configuration.nix.
This is because by setting some options to ânoâ, other options become an error and need to be âunsetâ. There is also the possibility that I remove something that I shouldnât so I want to change it gradually and notice when it happens.