Clang-ing the kernel
Jonni Bidwell shows you how to configure a mainline kernel, then compile it, artisan-style, with LLVM/Clang.
Linux is full of virtual dials, switches and frobs that can tweak, monitor or break pretty much every aspect of the system. You can meddle with GPU settings in /sys/class/drm, run programs at higher priority with the nice command or find out (and change) anything and everything about a running system by rummaging around in the /proc directory. Just because you can do something doesn’t necessarily mean you should, though. As a beginner, in this case, you almost certainly shouldn’t. Distros go to considerable effort to ship with defaults that will work well for most people. And for the most part, work well they do.
What we’re going to do today, then, might be seen as irresponsible. Because we’re going to build a kernel from scratch and replace the one our distro (in this case, Ubuntu) shipped with. The kernel is the beating heart of any Linux install, where the most privileged operations run and where direct communication with hardware takes place. We couldn’t get any more lowlevel if we tried, so be in no doubt that this is not for everyone. You won’t see magical performance gains or double your free memory. But you will gain insight into the vast spectrum of kernel subsystems. And you will get to build it with LLVM and Clang instead of the GNU compiler (GCC). Something that has only been easy to do for a couple of years.
The stock Ubuntu kernel is installed by the linuximage* packages. These are built by taking a source tarball from http://kernel.org (ideally from one of the long-term branches, but not always), applying various patches and then compiling. Some of these patches are backports of patches applied to newer kernel versions, some are Canonical’s own customisations and others are cherry-picked from the current state of kernel development at large. As a result, the 5.15 series kernels found in Ubuntu 22.04 don’t have a whole lot in common with the 5.15.93 (at the time of writing) sources found at http://kernel.org.
Hitting the mainline
It’s entirely possible to build a kernel based on the Ubuntu sources, but it’s equally possible to replace the Ubuntu kernel with a mainline build. Lots of people do this already, for reasons as diverse as improved hardware support to irrational dislike of Canonical kernels. Whatever your reason, our first step is to get the required build tools. Deep breath:
Jonni Bidwell once was technical editor for a popular Linux tabloid. Nowadays, he dutifully passes what earnings he makes to the all-demanding Canal & River Trust.
$ sudo apt install wget build-essential bison flex
libncurses-dev libssl-dev libelf-dev
That’s just what’s required to build with GCC. We also need LLVM and Clang, which together weigh in at about 700MB:
$ sudo apt install llvm clang clang-tools
The easiest way to get the latest kernel source is to download the tarball marked ‘stable’ from https:// kernel.org. At the time of writing, this is 6.1.11. Assuming the file landed in your Downloads folder, we can extract its contents (to a sensible location) with:
$ mkdir ~/lxfkernel
$ cd ~/lxfkernel
$ tar xavf ~/Downloads/linux-6.1.11.tar.xz
Now we can prepare a default kernel and dive into its manifold configuration options. If we don’t explicitly specify the Clang compiler here, plain old GCC is used.
$ cd linux-6.11/
$ make CC=clang defconfig
$ make CC=clang nconfig
There’s not enough space in this magazine, let alone on this page, to cover all the kernel options or which ones you might want to change. Do have a rummage around, though. You can get help for the currently highlighted option by pressing F2.
Start the build ceremony with:
$ make CC=clang LOCALVERSION=-lxf deb-pkg
-j$(nproc)
Here we add the -lxf suffix to the package version, so as not to clash with the official offering. The ugly last part of the command tells the build system to use all available threads on the system. There’s actually an official Ubuntu way to build kernels, but it only works with Ubuntu sources. Our way should work with any sources on any Debian-based distribution. Our ageing dual-core i7 laptop completed the build in about 20 minutes. And it would have taken four times longer (thanks to Hyperthreading) had we not used this option. Metrics aside, we’ll now get on with installing our kernel.
Testing our kernel
The previous package should have generated four DEB files in the parent directory. If you run ls .. , you should see something like:
../linux-headers-6.1.11-lxf_6.1.11-lxf-1_amd64.deb ../linux-image-6.1.11-lxf_6.1.11-lxf-1_amd64.deb ../linux-libc-dev_6.1.11-lxf-1_amd64.deb
Some other files will be generated, too, but you can ignore those. We only really need the kernel image, so let’s install it with:
$ sudo dpkg -i ../linux-image*.deb
You should see the GRUB configuration be regenerated, so let’s reboot and see whether it works. If it doesn’t, don’t fret, you can always push Esc during boot to get to the GRUB menu and launch the official kernel instead (choose Advanced Options For Ubuntu from the main menu). It’s worth checking at least that your network and other hardware all still work as there is much potential for regression. If you want to purge any trace of your custom kernel effort, just remove the package and everything will be put back to how it was:
$ sudo apt remove linux-image-6.11-lxf
It’ll probably take a couple of attempts in order to make successful, non-trivial changes to the kernel configuration. If you already have a kernel .config file from a previous version, you can copy it to a new kernel source directory and then use make oldconfig to bring it up to date. If you don’t want to be asked a whole lot of questions about new kernel options, and instead just want to accept the defaults, you can use make olddefconfig .