Introduction is a powerful script, published in 2010 by Steven Rostedt, long time Linux contributor and Ftrace maintainer. Readily available upstream, along with other useful scripts and tools, you can use it in different ways to automate the process of building, installing and testing the Linux kernel.


Ever since I restarted doing cross builds more frequently, I would occasionally forget to either set ARCH= or simply do a modules_install. Up until the point I actually figured out the problem I already had lost precious time. By then, I had no idea what the hell I was doing or what .config I was in. The solution came after watching Steven’s presentation “A unique way to maintain a Linux kernel subsystem” from last year’s Kernel Recipes conference.

Also, one of the things that I have been doing in the last months is to build and test stable release candidates and sometimes -next trees. Ktest provided me a nice way of doing clean builds and keeping track of my default configuration across different work trees. It became as simple as this:

cd $LINUX/next
ktest local
cd $LINUX/6.6.y
ktest local

Without having to worry about keeping different .config files in sync across directories.

Basic Setup1

Let’s start from the beginning, this is what I use as a wrapper in ~/.local/bin/ktest2:

if [ -z "$1" ]; then
    echo "usage: ktest <config>"
    exit 1

basepath=$HOME/ktest  # change this if you want it elsewhere
config=$(realpath "$basepath/$1.conf")

if [ ! -f "$config" ]; then
    echo "config file not found: $config"
    exit 1

$basepath/ $config

This way you can launch the script from any tree. Now, assuming you have a Linux git repository in $LINUX:

# LINUX=/path/to/mainline
cp -r $LINUX/tools/testing/ktest $HOME
mkdir -p $HOME/ktest/{build,config}/local
mkdir $HOME/ktest/scripts
cp /boot/config-6.6.9-amd64 $HOME/ktest/config/local/stable

Note that /boot/config-6.6.9-amd64 is just an example taken from Debian. It is a huge configuration carefully tailored by the Debian Kernel Team to support most of the hardware out there in the wild. You should probably trim that down using make localmodconfig for day to day usage.

This is an example of a standalone ktest configuration, put this in $HOME/ktest/local.conf (or wherever you put the ktest directory):

MACHINE = local

THIS_DIR = /home/rbmarliere/ktest


TEST_TYPE = build
BUILD_OPTIONS = -j$(nproc) bindeb-pkg
BUILD_TYPE = useconfig:${CONFIG_DIR}/stable

As you can see, the TEST_TYPE is the most simple one: it just builds the kernel. The BUILD_TYPE ensures that the stable configuration is used. The POST_BUILD points to a script that actually installs the generated .deb packages and update the boot loader. Add this to ~/.local/ktest/scripts/local_install:

if [ $# -ne 2 ]; then
    echo "usage: $0 <kernel version> <build output dir>"
    exit 1


pushd "$BUILD/.." || exit 1

sudo dpkg -i ./linux-{image,headers}*"$VERSION"*.deb && sudo update-grub
rm -rf ./*"$VERSION"*{deb,changes,buildinfo}
rm -rf ./*{upstream,libc}*{deb,changes,buildinfo}


Cross-compilation Setup

If you remember from the last post, I was experimenting with NFS and U-Boot. That is, the board will boot from the kernel I have available locally on my development host through tftp. Here is what I use as rpi32.conf:

MACHINE = rpi32
DTB = broadcom/bcm2837-rpi-3-b.dtb

INCLUDE include/defaults.conf

MAKE_CMD = CROSS_COMPILE=/usr/bin/arm-linux-gnueabihf- make ARCH=arm
BUILD_TARGET = arch/arm/boot/zImage

TEST_TYPE = build
BUILD_TYPE = allnoconfig

As you can see, the MAKE_CMD is responsible of making sure that the build as always made with the correct environment. Also, the build start point is an allnoconfig, but that’s because in my include/defaults.conf I have the following:

MIN_CONFIG = ${CONFIG_DIR}/config-min

In that file, I put the minimal configuration from before, with some added enabled symbols like NFS_FS, SERIAL_AMBA_PL011_CONSOLE, BCM_VIDEOCORE, etc. That file is the bare minimum to be able to boot the machine, the basis from which ktest will randomize the configuration if that is part of the test. But for this simple example, it will only build and install the kernel. This time, using a different script, though:

TFTP=$HOME/ftp  # or wherever...

if [ $# -lt 4 ]; then
    echo "usage: $0 <kernel version> <build output dir> <build target> <machine> [dtb]"
    exit 1


pushd $TFTP || exit 1

mkdir -p $MACHINE

pushd $MACHINE || exit 1

ln -sf linux-$VERSION linux

if [ -n "$DTB" ]; then
	cp $BUILD/$(dirname $TARGET)/dts/$DTB .
	ln -sf $(basename $DTB) dtb


This will put the dtb and zImage in $TFTP/$MACHINE/dtb and $TFTP/$MACHINE/linux as symlinks to the original files, which is where the U-Boot script expect to find them.

Virtual Machine Setup

This one is also straight forward and actually way better documented. Just make sure that you add the relevant entry to the boot loader with console=ttyS0 so that ktest is able to read it.


I only scratched the surface here, but you can also test random kernel configurations, bisect issues, run custom tests inside the machine, etc. So do go ahead and read the documentation for adapting it to your needs and make sure to review the examples too. You can also draw some inspiration from this repository. The goal of this post was to bring awareness to this excellent tool, seeing that it improved my workflow a lot after adopting it.


  1. These scripts are just a suggestion, based on the way I use the tool. Adapt to your own liking. ↩︎

  2. Make sure ~/.local/bin is in your $PATH and the script is executable. ↩︎