Building a Custom XanMod Kernel on Ubuntu 18.04

Ubuntu’s stock Linux kernel is fine for most users, but because Ubuntu follows a six-month release cycle, their kernels are always a release or three behind upstream. This can cause problems for users attempting to install Ubuntu on brand new hardware, or worse, force ultra-nerds to wait to play with the latest and greatest new kernel features!

Fuck that.

As mentioned previously, my main workstation — a Lenovo X1 Carbon (Kaby Lake) — boots XanMod kernels instead. XanMod’s kernels mirror the upstream development cycle, and as a bonus, include a number of exciting optimizations and patches to improve performance and expose up-and-coming features.

One recent patch pulls in the ability to utilize architecture-specific GCC optimizations when compiling a kernel. In general, compiler optimizations may not have much effect on runtime performance, however with something as foundational as a kernel, tiny improvements might add up quickly.

Before we dive down this rabbit hole, let’s get some background covered.

One Kernel, Lots of Hardware

The Linux kernel is a very large piece of software. It contains drivers and special handling for all sorts of disparate hardware combinations. By and large, this is a good thing; it allows all sorts of different machines to boot a Linux kernel without undo fiddling. Pre-compiled kernels — like Ubuntu stock or XanMod — keep most of this code around because they cannot anticipate the kind of hardware their users might be using.

But one user is not all users. Your computer is not a sum of all histories; it is what it is. My Lenovo laptop, for example, contains a Kaby Lake Intel processor, which it also uses for graphics. As such, I don’t actually need any kernel support for AMD/Radeon/Nouveau or the like.

Removing unused drivers and conditional handling, while also applying any compiler optimizations for your specific CPU architecture, will result in a smaller and faster kernel, even if every other setting is left alone.

One Kernel, Your Hardware

Compiling a kernel from scratch might seem daunting, but the process is actually quite easy. We’re just trimming a bit of excess! For this tutorial, we’ll be using the XanMod 4.17 source tree. (If you are from the future, be sure to change the branch to whatever is current when you come from.)

First things first, you’ll probably need to install some build dependencies. Depending on the state of your system, you might need a few additional packages, but don’t worry, the build process will let you know if anything is missing, so just think of it as a game of Whack-a-Mole. Regardless, this will get you started:

# The newer your build tools, the better the build. This repo will
# give you GCC 8.1, etc.
sudo add-apt-repository ppa:ubuntu-toolchain-r/test

# Just in case sources didn't update.
sudo apt-get update

# Install the basics. Again, you might need a few more
# things, but the build process will let you know.
sudo apt-get install build-essential bison flex gcc-8 g++-8

# It is also good to verify which version of GCC runs
# by default.
gcc -v

# If an older version of GCC pops up, you should update
# the default choice. update-alternatives is the preferred
# way to handle stuff like this, which you can achieve with
# something like the following:
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-8 800 --slave /usr/bin/g++ g++ /usr/bin/g++-8
sudo update-alternatives --config gcc

Next, you’ll want to grab a kernel source. This example uses XanMod, but the process — if not the patches — will be the same with any tree.

# Again, if you're reading this in the future, change
# the branch.
git clone https://github.com/xanmod/linux.git xanmod --branch 4.17

# Move into the directory.
cd xanmod

# This shouldn't be needed, but just to be safe:
make clean

# Build and launch the configuration wizard. On first run, this
# should reflect the default XanMod settings, so you don't need
# to worry about replication; just target the couple changes you
# want to make.
make menuconfig

Let’s begin our customization by enabling GCC optimization. You can do this by selecting Processor type and features --> from the main menu, then Processor Family (XYZ) --> from the next screen. Unfortunately for me, GCC does not yet feature Kaby Lake-specific optimizations, so I had to choose the next best thing: Skylake. Meh. Close enough.

Next up, let’s remove any unused display drivers, which will shave a lot off the compiled kernel’s disk footprint. Get back to the original menu, then click into Device Drivers --> and look for Graphics Support -->. As a general rule, if you are unsure about whether or not a particular driver is used, leave it be. But for my machine, AMD*, Nouveau*, and Radeon* definitely serve no purpose, so I disabled them.

If you feel like stopping here, that’s A-OK! Trimming the graphics stack carries you a long way toward a streamlined kernel. But if you want to continue on, I recommend taking a look at the input devices — trimming things like joysticks, tablets, and touchscreens — and filesystems — trimming things like XFS, ResiserFS, F2FS, Apple HFS, etc. Again, if you are unsure what something is or whether it is needed, leave it be, or mark it M to have it loaded dynamically as needed.

When you’re done, select the Save option.

For the brave and/or simple, it is also possible to automatically remove any modules from the build config which have not been loaded by the current (live) system. This feature will give you a lean, mean kernel, but can also accidentally remove modules that are only loaded occasionally. With that in mind, proceed with caution, and try to connect any external devices and launch any mod-dependent software before running the following command:

make localmodconfig

Any module deemed unnecessary will automatically be removed from your existing build config.

Build and Install!

To make life easier, we’ll be compiling the kernel as an installable .deb package.

There are two things to note in the simple command below:

  • -j4 refers to the number of threads your CPU supports. If yours supports 8 threads, for example, use -j8 instead.
  • As written, -custom will be appended to the compiled kernel files, e.g. vmlinuz-4.17-xanmod1-custom. If you’d rather use a different identifier, just replace it, e.g. LOCALVERSION=-dog would result in vmlinuz-4.17-xanmod1-dog.
make -j4 deb-pkg LOCALVERSION=-custom

Compiling the Linux kernel takes some time. Grab yourself a coffee and catch up on your favorite TV. When it is done, move up one directory to find the compiled packages. You can then run the following to install them.

sudo dpkg -i ../linux-headers*.deb ../linux-image*.deb

Assuming there were no installation errors, reboot and see how you did! (If there are any boot-related issues or other problems, re-reboot to a known-good kernel, then begin again from make menuconfig. This will default to the settings you had previously chosen, so make any changes you want, rebuild, re-re-reboot, until everything is just how you want it.

Results

At the very least, your effort spent customizing and compiling your own kernel should free up some disk space on your /boot partition. Of course, your mileage will vary depending on your compression settings, etc.

For my laptop — with modest Zstd initramfs compression — the GCC optimizations and manual pruning via make menuconfig managed to shave 12.40MB off the stock XanMod kernel. Combined with make localmodconfig, the savings skyrocketed to 40.35MB. That may not seem like much, but given a total EFI partition size of 125MB, that is a very welcome return on investment.

The performance impacts of such efforts will vary by machine, but if nothing else, boot times should be slightly faster. With less to decompress and load, my system boots between 1-1.5 seconds faster. The GCC optimization flags don’t accomplish much for Kaby Lake processors yet, but systems with older architectures could see efficiency boosts of 3-10% under certain types of workloads.