iBoyko - "sort of" a website

How to Build the Latest Mainline Linux Kernel for Ubuntu

In this article we show how to build and install the latest mainline Linux kernel for Ubuntu. Non-latest kernel versions and other Linux distributions are also covered.

Posted by Yakov Boyko on November 18, 2021

For many Linux users there comes a point at which they need to build the Linux kernel from source code. Frequently, it happens because a certain feature has already been implemented in source code, but has not been released to the public in an official and stable release. At other times, the reason may have to do with a desire to obtain a kernel which has a certain set of configuration features. Yet at others, it may be a basic desire to tinker and learn how Linux works on a deeper level. Building the needed kernel from source code covers all of those needs. Aside from that, it gives a psychological reward familiar to any engineer who has ever built something or learned how something worked.

In this article we shall cover how one can build the latest Linux kernel from source code. More particularly, we shall build a kernel for Ubuntu 20.04. The process will be very similar for other Linux distributions (such as Arch, Manjaro, Debian, etc). The only differences will probably come in during interaction with package managers and some directory locations.

Background and Terminology

Before we proceed with the build process, it is useful to note some terminology. First of all, there is a thing called "Linux kernel". Kernel is the main component of the Linux operating system and is responsible for such things as memory management, device management, process management, system calls and security, etc. It is called a kernel because - like a seed of a nut - it sits in middle of it all and performs the main function. Roughly speaking, the kernel is a software which sits atop the hardware, which it itself manages. In addition, the kernel allows various user processes to communicate with each other, use memory, access devices, etc. That is, it is somewhat of a gasket between the (user) processes and the hardware.

When speaking about Linux kernels, one needs to distinguish between a mainline Linux kernel and a distribution kernel. A mainline kernel is something which contains just the Linux kernel and is developed and supported by Linus Torvalds and company. The latest Linux mainline kernel is located here. This kernel is where all other Linux distributions derive their code from. However, instead of simply compiling and using the mainline code - which maybe unstable - they can introduce changes to it, patch it, tune it and otherwise modify it. That is, a distribution kernel is a mainline kernel, which has somehow been modified to taylor it to the needs of that distribution, a particular community or even a person. Ubuntu, Debian, Manjaro, Arch, Gentoo are all distributions and have their own distribution kernels. To see what features were added in different kernel versions, go to THIS helpful page. 

In our example, we shall proceed to build an Ubuntu kernel, but we shall do it from the mainline source code. This mean that prior to starting the build, we'll need to tell the builder to build it for a use on an Ubuntu system. As you shall see later, this is done by providing the buillder with a configuration file. This file (as well as the patches) is what makes the difference between a mainline built and an Ubuntu built. An Ubuntu build will have a lot more features enabled than a basic mainline build.

Build Process

Having gotten some terminlogy and theory out of the way, let's proceed with a build. I assume you are running an Ubuntu 20.04 or something similar. If you are on another distribution, you can use this tutorial as a close-enough guide, but may need to tweak some commands here and there. Doing things as indicated in this tutorial will allow one to test the latest mainline (or any other version) of the kernel.

First of all, let us create a workspace and clone the kernel code into it.

mkdir ~/SourceCode; cd ~/SourceCode; sudo apt install git; git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git ; cd linux

Now that we have the entire repository with source code, we can build any version of the kernel from it. Adding the concept of configuration file (see above), we can technically build any kernel version from it. 

Git (source-control system) has a concept of tags, which are labels for different stages of development and indicate various releases of the product. To check the available kernel versions, run something as follows. At the time of this writing, the most recent kernel version is 5.16, so modify the search pattern accrodingly

git tag -l "v5*"

You should then see something like this


These are various tags, corresponding to the kernel versions and releases. Let us builld the most recent one. This means that we shall not checkout any tags and instead build from the master branch, which is selected by default. In case you want a different version, checkout the needed tag by running something like this (this particular command will revert the repository back to the its state for the version 5.15):

git checkout v5.15

We shall be doing an out-of-tree built, which means that the resulting built files will not be mixed with the source code. For this, we need a separate directory. Let's create it. 

mkdir -p ../builds/amd64

In addition, we need to install a few dependencies, which are required to build the kernel. Here are a few common packages that need to be installed prior to the commencement of the build. In case there is an issue with missing packages later on, one needs to resolve them in similar fashion.

sudo apt install libssl-dev
sudo apt install kernel-package
sudo apt install patch
sudo apt install dwarves

Since we want our kernel to run on Ubuntu and be an Ubuntu kernel, we need to do two things:

1) copy the current (Ubuntu) kernel configuration to the build directory to let the builder know what we want to build

cp /boot/config-$(uname -r) ../builds/amd64/.config

2) update the copied configuration with any new config options available in the latest kernel. We update it by running the first stage of the build

make O=../builds/amd64 olddefconfig

Next, we proceed with a shortcut, which instructs the builder to avoid building very large and time consuming debugging packages. If you need these packages for some reason, obviously skip these steps.

pushd ../builds; wget https://iam.tj/projects/ubuntu/linux-deb-no-debug.patch ; popd

(Credit for this patch goes to a very helpful person on an Ubuntu IRC channel :)

Let us apply the patch to the configuration:

patch -p1 <../builds/linux-deb-no-debug.patch

To see the difference it introduced, run the following command (git diff) and inspect its output

(base) ➜  linux git:(master) ✗ git diff
diff --git a/scripts/package/builddeb b/scripts/package/builddeb
index 91a502bb97e8..707d4c72596b 100755
--- a/scripts/package/builddeb
+++ b/scripts/package/builddeb
@@ -220,7 +220,7 @@ fi
 create_package "$packagename" "$tmpdir"
-if [ -n "$BUILD_DEBUG" ] ; then
+if [ -z "$PKG_DEBUG" -a -n "$BUILD_DEBUG" ] ; then
        # Build debug package
        # Different tools want the image in different locations
        # perf

That is, the patch creates and additional environment variable called PKG_DEBUG which, when set, prevents the creation of debugging packages when building the debianised packages (Ubuntu).

Hence, each time one reopens the shell (Bash, ZSH, etc) to compile the kernel, he needs to set this variable as follows

export PKG_DEBUG=1

A couple of last things to do consists of disabling the system trusted keys, so we can avoid an error due to copying an Ubuntu configuration file.

(Note: these steps may not be needed on your machine and configuration, as well as on newer versions of the mainline kernel. Adapt accordingly and proceed without these commands first!):

scripts/config --file ../builds/amd64/.config --disable SYSTEM_TRUSTED_KEYS
scripts/config --file ../builds/amd64/.config --disable CONFIG_DEBUG_INFO_BTF

At this point, we are ready to launch the build. Depending on the number of cores of your processor your command may differ from mine. For an eight-core processor, I am instructing it to use all but one core for the compilation.

make -j7 O=../builds/amd64 bindeb-pkg |& tee /tmp/kernel-build.log

Estimated time of completion is anywere from 5 minutes to over an hour, depending on the performance of your workstation. You most likely have time for a cup of tea, a nap or a peak into the rest of this article!

The command above should generate three .deb files in the ../builds directory, which will be the files for the kernel and its headers.

If the build proceeds without errors or issues, make sure that you have three files in your ../builds directory afterwards

ls -latr ../builds/*.deb
(base) ➜  linux git:(master) ✗ ls -latr ../builds/*.deb
-rw-r--r-- 1 user user  8565504 Nov 18 14:55 ../builds/linux-headers-5.16.0-rc1+_5.16.0-rc1+-4_amd64.deb
-rw-r--r-- 1 user user  1210356 Nov 18 14:55 ../builds/linux-libc-dev_5.16.0-rc1+-4_amd64.deb
-rw-r--r-- 1 user user 69102328 Nov 18 14:55 ../builds/linux-image-5.16.0-rc1+_5.16.0-rc1+-4_amd64.deb

To install the newly built kernel, run the following command

sudo dpkg -i ../builds/linux*5.16.0-rc1+*.deb

If this completes successfully, then the new kernel has been installed. After a reboot, the Linux should start with this kernel version since its version is higher than anything else installed. However, to be sure, hold Esc during reboot to get to the Grub menu. Select the correct kernel version afterwards. The work of building and installing the newest mainline kernel is now complete.

How to stay updated on changes in the mainline kernel?

In the baseline directory called .linux, do git pull, which will fetch the latest upstream changes and merge into the current working directory. 

To tinker with kernel build options, run make O=../builds/amd64 menuconfig. This allows one to see all configuration options. For almost every option, choosing "help" will explain the option.

Run make O=../builds/amd64 olddefconfig in case new configurations have been added to the newest mainline kernel. olddefconfig means take the current (old) .config and add to it all new configs with their default setting.

In general, the benefit of keeping up with the changes comes down to getting the most recent changes, bug fixes, possible performance improvements, new features and new device support.