Compiling a custom Linux Kernel
Last week, I finally gave in and tried my luck by compiling a custom Linux kernel. After several days of work and a lot of hair-pulling…
A Linux user, a vegan, and an atheist walk into a bar. I know because they told everyone.
During these past few months, I have been switching between various kernels depending on the work I wanted to do. Sometimes I needed an IOMMU patch, some other times I wanted to try some Linux gaming, and, more often than not, I was too slow to select the correct GRUB entry and ended up using the default Arch Linux kernel.
Last week, I finally gave in and tried my luck by compiling a custom kernel tailored specifically for my new laptop. After several days of work and a lot of hair-pulling, I managed to boot into a kernel which only had support for the exact hardware I needed.
Besides the slightly improved boot times, I also saw a surprising increase in battery life (7.9 Watts used when idle compared to 11) and about 500MBs more free RAM.
In this post, I will be explaining the main steps I followed when creating this kernel, hopefully helping anyone that finds themselves in a similar situation. You can find the finished configuration in this project's Git repo.
Post contents
Preparation
Before you begin, I recommend installing a normal Linux distribution with good support for your hardware and making sure that everything is working how it's supposed to. In my case, I used Arch Linux with the latest kernel.
Once you confirm that everything is working, you should also store the output of some commands so that you have a working reference point when booting with your custom kernel:
lspci -nnkkvvv
for verbose hardware and driver detailslsmod
for loaded kernel moduleslsusb
for connected devices on various USB busseslscpu
for CPU information
You should also have some clear goals before you start working on your kernel's configuration. I originally made the mistake of going in blind, which made me waste several hours debugging boot errors, changing previously-set options, and eventually forcing me to restart from scratch.
Installing build dependencies
Once you are ready to start, install the packages you need for building the kernel. If you are using Arch, this can be easily done by running:
Acquiring the source code
After installation is complete, go ahead and download the latest kernel source code. You can do that by either going to https://www.kernel.org/, grabbing the latest tarball, and unpacking it, or by using git and pulling from the official repo:
You should also make sure that the kernel tree is clean by running make mrproper
in the downloaded directory.
Configuration
Now it's finally time to configure the kernel. What I recommend doing is getting your distribution's default .config
by running zcat /proc/config.gz > .config
, and compiling it to act as a sanity check for further .config
changes.
Advanced configuration
Once you've made sure that the default config is working, you can go ahead and run make clean
to remove the previous build and make nconfig
to open the configuration menu.
The sheer number of choices that will appear in front of you will be overwhelming, but don't let that discourage you. If you use a slow and methodic approach you'll realize that while compiling a custom kernel is time-consuming, it is certainly not hard.
Just a word of advice: Do not try and configure everything in one run. There is a high chance that you will break something and spend more time debugging it than if you were to make small, gradual changes, recompiling every once in a while to check if everything is working.
When I was creating my config, I had 3 main goals in mind:
- Keep it as minimal as possible
- Improve performance as much as possible without harming battery life
- Disable any logging / debugging capabilities that might increase runtime overhead
I'm not going to go through every choice I made since most of the options are architecture-dependent and analyzing all use-cases would take months. Even if I did, if you copied my config without understanding the functionality behind each feature, you would most likely end up with an unbootable system.
There are a couple of resources you can use to learn what each option does:
- The in-built help text, available by pressing
h
- DOTSLASHLINUX's kernel configuration guide
- The Gentoo Wiki
- Good ol' Google
The general strategy I followed when working on this project was to first get the easy things out of the way: These included options like CPU architecture support, compression, filesystems, etc. I then worked my way through one submenu at a time, recompiling the kernel to make sure I didn't break anything. I recommend leaving the behemoth that is the Device Drivers menu for last since that will take the most time and contains multiple options dependent on other settings.
Applying Patches
Now would also be a good time to apply any needed patches. In my case, I wanted to apply an ACS Override patch so that I could have more control over my IOMMU Groups. You can easily apply .patch
files by moving them to the kernel source directory and running:
If you don't get any rejects, you are good to go for compilation.
Compilation
Now that you've made some changes to your config, it's time to compile it. Go ahead and run the following commands:
Each step should take a decent amount of time, depending on how large the kernel is. In my case, it took about 10 minutes on a Ryzen 7 5800H. If everything went well, you should notice a new arch/x86/boot/bzImage
file.
Installation
Now that you have finished compiling your kernel, it is finally time to install it.
Kernel
Go ahead and run the following command. You can name the resulting file as you wish, provided that it is prefixed with vmlinuz
. In my case, I named it vmlinuz-linux-eirene
:
Initial RAM disk
Once you have copied your kernel to the /boot
directory, you should also generate an initial RAM disk. You can easily do that by copying the default kernel's preset:
After that, edit the new file and make sure to change it so it matches the name you selected.
Bootloader
Finally, add an entry for your new kernel in your bootloader's configuration file. If you are using GRUB, you can simply run the following command:
If you didn't get any errors, you should be able to select the newly-compiled kernel the next time you boot your computer!
Troubleshooting
One easy way to check for errors is to look at your system's journal:
Keep in mind that not everything shown here is important, but it can be a good lead for finding the root cause of an issue. In the worst-case scenario, you can simply backtrack and try recently-changed config options one by one to see which one creates the problem.
Conclusion
Even though configuring a kernel is challenging, the feeling of booting it for the first time and seeing the resulting performance/battery life increase is definitely satisfying.
Maintaining that kernel is an entirely different story, but that can be fun as well, depending on how much you hate yourself, your free time, and your mental health 😀.
Happy coding and happy new year!