Building a tickless Ubuntu Kernel

For a while I have been working on a pet project with the goal of learning more about the limits of low-latency software development in Go.

Recently, I wanted to benchmark some of aspects of this on a tickless kernel. Since most of my development workflow is Ubuntu based and I have no compelling reason to change that, the most straightforward option was to compile the Ubuntu kernel with nohz_full option.

After a quick DuckDuckGo (is that a verb yet?), I was mostly able to achieve this by following intructions provided here. The author has many other interesting writings, definitely worth checking out.

I write this as mostly a tweaked copy of the same instructions, complemented with a few that were missing and/or needed a bit of tweaking (some of which might be due to my eccentric setup).

Uncomment source repositories

This step might be optional depending on your setup:

Let’s quickly backup our sources.list

1
sudo cp /etc/apt/sources.list /etc/apt/sources.list-

Open /etc/apt/sources.list and uncomment the deb-src lines.

Install required dependencies

1
2
3
4
sudo apt update
sudo apt build-dep linux linux-image-lowlatency linux-image-generic
sudo apt install devscripts
sudo apt install libcap-dev

If you have problems with the above you might also need to:

1
sudo apt --fix-broken install

Clone kernel repo

The official site is very slow, but let’s use that anyway. If you’d like to clone from a faster location, please check the article mentioned above.

The last line picks one version ahead, for ease of recognition and for the newly compiled kernel to be the default at boot.

1
2
3
4
5
mkdir kernel-workspace
cd kernel-workspace
git clone git://kernel.ubuntu.com/ubuntu/ubuntu-focal.git
cd ubuntu-focal
git checkout origin/hwe-5.15-next 

Update the config

Feel free to choose your own editor for this.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
LANG=C fakeroot debian/rules debian/control
fakeroot debian/rules clean

sed -i 's/CONFIG_PREEMPT_VOLUNTARY=y//' debian.hwe-5.15/config/amd64/config.flavour.generic
echo "CONFIG_PREEMPT_NONE=y" >> debian.hwe-5.15/config/amd64/config.flavour.generic
echo "CONFIG_NO_HZ_FULL=y" >> debian.hwe-5.15/config/amd64/config.flavour.generic

sed -i 's/CONFIG_PREEMPT_VOLUNTARY=y//' debian.hwe-5.15/config/amd64/config.flavour.lowlatency
echo "CONFIG_PREEMPT_NONE=y" >> debian.hwe-5.15/config/amd64/config.flavour.lowlatency
echo "CONFIG_NO_HZ_FULL=y" >> debian.hwe-5.15/config/amd64/config.flavour.lowlatency

fakeroot debian/rules editconfigs

Select n for edit config options of both flavors. This will provide a bunch of questions with correct defaults already selected so go ahead and keep pressing [Enter] until the end.

The last few messages will contain a bunch of errors. To fix them run:

1
nvim debian.hwe-5.15/config/annotations

Change the appropriate params for the configs shown in the errors. We only need to change either the and64 or the amd64-generic and amd64-lowlatency options, as appropriate.

When done, run the command fakeroot debian/rules editconfigs again. This should show no errors and all checks should pass. Repeat as necessary.

Build the kernel

That’s it! Time to build the kernel.

Choose the number of parallel jobs as appropriate. The more the cores you have, the more the jobs you can run in parallel and the faster this will go. If you don’t have a million core machine, perhaps best to run this overnight.

1
DEB_BUILD_OPTIONS=parallel=16 flavours=generic no_dumpfile=1 LANG=C fakeroot debian/rules binary
1
2
3
4
5
...

This will take a while. Get some sleep, watch a couple of movies, whatever.

...

If everything goes well, the build should have completed without errors. Go one directory up.

1
2
cd ..
ls -lah

There’s the shiny new compiled kernel packages. Install some/all of them.

1
sudo dpkg -i packages-you-want-to-install

If there’s no UEFI secure boot, this is it. reboot into the new kernel.

UEFI

This is an optional step depending on whether UEFI secure boot is enabled. The instructions are based on the ones available here

Backup

1
tar -cf efi.tar.gz /boot/efi

Create a keypair to sign the kernel

1
2
mkdir sign
cd sign

Change the country, state, locality, org, common name and email values and run:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
cat > mok.cnf << EOF                                                                                                                                  19:24:33
# THis definition stops the following lines failing if HOME isn't
# defined.
HOME                    = .
RANDFILE                = $ENV::HOME/.rnd
[ req ]
distinguished_name      = req_distinguished_name
x509_extensions         = v3
string_mask             = utf8only
prompt                  = no

[ req_distinguished_name ]
countryName             = SomeCountry
stateOrProvinceName     = SomeState
localityName            = SomeLocality
0.organizationName      = SomeOrg
commonName              = SomeKey
emailAddress            = some@email.com

[ v3 ]
subjectKeyIdentifier    = hash
authorityKeyIdentifier  = keyid:always,issuer
basicConstraints        = critical,CA:FALSE
extendedKeyUsage        = codeSigning,1.3.6.1.4.1.311.10.3.6
nsComment               = "OpenSSL Generated Certificate"OA
EOF

openssl req -config ./mok.cnf -new -x509 -newkey rsa:2048 -nodes -days 36500 -outform DER -keyout "MOK.priv" -out "MOK.der"

Run the following and remember your password for enrollment. Write it on a piece of paper perhaps since papssword manager will not be available during MOK enrollment.

1
sudo mokutil --import MOK.der

Time to reboot.

1
reboot

On reboot you’ll see a blue MOK management screen. Choose “Enroll MOK” and then continue with the steps to enroll the newly created key. Enter the password from the enrollment step when asked.

Sign the kernel

Change into the sign directory created in the previous step.

1
2
3
4
5
6
openssl x509 -in MOK.der -inform DER -outform PEM -out MOK.pem
sudo sbsign --key MOK.priv --cert MOK.pem /boot/vmlinuz-5.15.0-53-generic --output /boot/vmlinuz-5.15.0-53-generic.signed
sudo mv /boot/initrd.img-5.15.0-53-generic /boot/initrd.img-5.15.0-53-generic.signed
sudo rm /boot/vmlinuz-5.15.0-53-generic
sudo update-grub
reboot

That’s it. You should be booting into the new signed tickless kernel.