In this article, you will learn how to rebuild the kernel Image for the Raspberry Pi.
“In computing, the kernel is a computer program that manages I/O (input/output) requests from software and translates them into data processing instructions for the central processing unit and other electronic components of a computer. The kernel is a fundamental part of a modern computer’s operating system.”
Wikipedia
The kernel is a vital part of any operating system; it bridges the computer hardware and the software on top. It contains all the drivers/modules required to work with the hardware. Sometimes, if you want a very specialist piece of hardware to work, you may need to compile your kernel.
For many of us, the concept of creating a custom Linux kernel might might sound a bit unfamiliar to some of us. We might have heard about it but never really tried it. After all, why bother? This additional step can seem unnecessary, especially when the default options work fine. However, let me illustrate why it’s worth your while.
A use case for a custom kernel
To make it easier for people to experiment with robotics, real-time programming, and all real-time aspects. ROS 2 Real-Time Working Group developed ros-realtime-rpi4-image: A flashable image builder for the Raspberry Pi 4 with ROS 2 and real-time kernel preinstalled. In many robotics applications, Part of the code must complete within a very strict deadline. A typical example is an inverted pendulum controller. If one iteration of the code has very high latency and does not complete in time, it may cause the controller to become unstable, creating some safety hazards. The stock Linux kernel traditionally does not provide an upper bound on scheduling latency. This can cause significant issues for real-time applications. Fortunately, there’s a project known as PREEMPT_RT that patches the Linux kernel such that it minimizes scheduling latency. Empirically, this modified kernel can be adequate for a wide variety of real-time applications, including many applications in robotics. In this case, it used to be the only way to get the kernel to compile ourselves by patching it.
Before we dive in, It’s essential to understand the process of kernel compilation. Tailoring the kernel to suit specific project requirements can lead to a significant boost in performance and functionality. This guide would be a get started towards gaining an understanding of the inner workings of the operating system and how to make changes to it. It will give an invaluable perspective on how computers work behind the scenes, revealing a new layer of understanding to apply to any software engineering practice.
Two methods for custom kernel compilation
There are generally two methods to compile the kernel for Raspberry Pi.
- Cross-Compilation
- Compilation on the Raspberry Pi
Cross-Compilation and Compilation on the Raspberry Pi itself. Each method has its advantages and use cases.
Method 1: Cross-Compilation
Cross-compilation involves building the kernel on a separate, more powerful machine and then transferring the compiled kernel image to the Raspberry Pi.
- Faster compilation times, especially on a powerful desktop or server.
- Allows for more complex kernel customization without straining the Raspberry Pi’s resources.
Method 2: Compilation on the Raspberry Pi
This method involves compiling the kernel directly on the Raspberry Pi itself.
- There is no need for a separate development machine.
- It is a more straightforward setup for beginners or those without access to a powerful development machine.
As building the kernel on Raspberry Pi itself could take several hours due to relatively low processing power, we will be using a Linux machine to cross-compile the kernel.
I strongly recommend using Linux as the desktop operating system for embedded Linux development for multiple reasons.
- All community tools are developed and designed to run on Linux. Trying to use them on other operating systems (Windows, Mac OS X) will lead to trouble.
- As Linux also runs on the embedded device, all the knowledge gained from using Linux on the desktop will apply similarly to the embedded device.
- If you are stuck with a Windows desktop, at least you should use GNU/Linux in a virtual machine (such as VirtualBox, which is open source), though there could be a minor performance penalty. With Windows 10, you can also run your favourite native Linux distro through Windows Subsystem for Linux (WSL2)
Desktop Linux distribution
To get started, download any good and sufficiently recent Linux desktop distribution that can be used for the development workstation, including Debian, Fedora, openSUSE, Trisquel, etc.
I prefer Debian; it’s a free operating system used by a wide range of organizations, large and small, as well as thousands of volunteers worldwide who work together on the Debian operating system, prioritizing Free and Open Source Software.
Make sure that you obtain a Linux distribution that is compatible with your hardware. For example, you might select a 32-bit i386 image or a 64-bit amd64 image.
For example, if you want to start with the Debian distribution, you can download an ISO-formatted image that you would use to install Debian Linux from https://www.debian.org/distrib/
Host vs. target
When doing embedded development, there is always a split between the host and the development workstation, which is typically a powerful PC. The target is the embedded system under development. These are connected by various means: almost always a serial line for debugging purposes, frequently a networking connection, and sometimes a JTAG interface for low-level debugging.
Serial line communication program
An essential tool for embedded development is a serial line communication program, like HyperTerminal in Windows. There are multiple options available in Linux: Minicom
, Picocom
, Gtkterm
, Putty
, screen
and the new tio
(https://github.com/tio/tio).
- I recommend using the simplest of them:
Picocom
- Installation with
sudo apt install picocom
- Run with
picocom -b BAUD_RATE /dev/SERIAL_DEVICE
. - Exit with
[Ctrl][a] [Ctrl][x]
- Installation with
-
SERIAL_DEVICE
is typically-
ttyUSBx
for USB to serial converters -
ttySx
for real serial ports
-
- Most frequent command:
picocom -b 115200 /dev/ttyUSB0
Cross-Compiling the Kernel
First, you will need a suitable Linux cross-compilation host. I tend to use Debian; since Raspberry Pi OS is also a Debian distribution, all aspects are similar, such as the command lines.
You can use VirtualBox (or VMWare) on Windows or install it directly onto your computer. For reference, you can follow the instructions online.
The latest versions of Debian have excellent support for cross-building through Debian Cross-toolchains.
Toolchains are set of software development tools and libraries (such as GCC, gdb, glibc ) that are chained together to enable you to build executable code on one operating system on one type of machine, such as a 64-bit Linux OS on an Intel x86 machine, but to execute them on a different operating system and a different architecture, such as a 32-bit Linux OS on an ARM device.
Debian Cross toolchain is a good starting point. We can list the available toolchains as follows:
debian@bookworm:~$ apt-cache search cross-build-essential
crossbuild-essential-amd64 - Informational list of cross-build-essential packages
crossbuild-essential-arm64 - Informational list of cross-build-essential packages
crossbuild-essential-armel - Informational list of cross-build-essential packages
crossbuild-essential-armhf - Informational list of cross-build-essential packages
crossbuild-essential-i386 - Informational list of cross-build-essential packages
crossbuild-essential-powerpc - Informational list of cross-build-essential packages
crossbuild-essential-ppc64el - Informational list of cross-build-essential packages
crossbuild-essential-s390x - Informational list of cross-build-essential packages
crossbuild-essential-mips - Informational list of cross-build-essential packages
crossbuild-essential-mips64 - Informational list of cross-build-essential packages
crossbuild-essential-mips64el - Informational list of cross-build-essential packages
crossbuild-essential-mips64r6 - Informational list of cross-build-essential packages
crossbuild-essential-mips64r6el - Informational list of cross-build-essential packages
crossbuild-essential-mipsel - Informational list of cross-build-essential packages
crossbuild-essential-mipsr6 - Informational list of cross-build-essential packages
crossbuild-essential-mipsr6el - Informational list of cross-build-essential packages
The primary purpose of these packages is to allow the cross-compiling of Linux programs for different architecture distributions, but these can also be used for bare metal programming. One thing to be aware of is that the compiler, by default, tries to link the Linux standard C libraries that, in bare metal programming, have no use.
Install Required Dependencies and Toolchain
To build the sources for cross-compilation, make sure you have the dependencies needed on your machine by executing:
sudo apt install git bc bison flex libssl-dev make libc6-dev libncurses5-dev
If you find you need other things, please submit a pull request to change the documentation.
Install the 32-bit Toolchain for a 32-bit Kernel
sudo apt install crossbuild-essential-armhf
Install the 64-bit Toolchain for a 64-bit Kernel
sudo apt install crossbuild-essential-arm64
Get the Kernel Sources
To download the minimal source tree for the current branch, run:
git clone --depth=1 https://github.com/raspberrypi/linux
Check here above for instructions on how to choose a different branch.
Build sources
Enter the following commands to build the sources and Device Tree files:
32-bit Configs
For Raspberry Pi 1, Zero and Zero W, and Raspberry Pi Compute Module 1:
cd linux
KERNEL=kernel
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- bcmrpi_defconfig
For Raspberry Pi 2, 3, 3+ and Zero 2 W, and Raspberry Pi Compute Modules 3 and 3+:
cd linux
KERNEL=kernel7
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- bcm2709_defconfig
For Raspberry Pi 4 and 400 and Raspberry Pi Compute Module 4:
cd linux
KERNEL=kernel7l
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- bcm2711_defconfig
64-bit Configs
For Raspberry Pi 3, 3+, 4, 400 and Zero 2 W, and Raspberry Pi Compute Modules 3, 3+ and 4:
cd linux
KERNEL=kernel8
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- bcm2711_defconfig
Build with Configs
For all 32-bit Builds
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zImage modules dtbs
For all 64-bit Builds
NOTE: Note the difference between Image targets between 32 and 64-bit.
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- Image modules dtbs
Install Directly onto the SD Card
Having built the kernel, you need to copy it onto your Raspberry Pi and install the modules; this is best done directly using an SD card reader.
The SD card is an essential part of the Embedded Linux Systems. It’s often necessary to copy(or “flash”) the software from our machine to an SD card for use in an Embedded Linux System or to back up projects to the entire SD card.
choosing the wrong drive will likely result in data loss or, even worse, harm our machine.
My machine has an Integrated SD card reader, and a multiport USB unit connects it and will be presented as, where X is a single letter. The first hard drive is often /dev/sda. In most of the machines with SD card slots, this will often be /dev/mmcblk0.
First, use lsblk
before and after plugging in your SD card to identify it. You should end up with something a lot like this:
sdb
sdb1
sdb2
with sdb1
being the FAT
filesystem (boot) partition, and sdb2
being the ext4
filesystem (root) partition.
Mount these first, adjusting the partition letter as necessary:
mkdir mnt
mkdir mnt/fat32
mkdir mnt/ext4
sudo mount /dev/sdb1 mnt/fat32
sudo mount /dev/sdb2 mnt/ext4
NOTE: You should adjust the drive letter appropriately for your setup, e.g. if your SD card appears as /dev/sdc
instead of /dev/sdb
.
Next, install the kernel modules onto the SD card:
For 32-bit
sudo env PATH=$PATH make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- INSTALL_MOD_PATH=mnt/ext4 modules_install
For 64-bit
sudo env PATH=$PATH make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- INSTALL_MOD_PATH=mnt/ext4 modules_install
Finally, copy the kernel and Device Tree blobs onto the SD card, making sure to back up your old kernel:
For 32-bit
sudo cp mnt/fat32/$KERNEL.img mnt/fat32/$KERNEL-backup.img
sudo cp arch/arm/boot/zImage mnt/fat32/$KERNEL.img
sudo cp arch/arm/boot/dts/*.dtb mnt/fat32/
sudo cp arch/arm/boot/dts/overlays/*.dtb* mnt/fat32/overlays/
sudo cp arch/arm/boot/dts/overlays/README mnt/fat32/overlays/
sudo umount mnt/fat32
sudo umount mnt/ext4
For 64-bit
sudo cp mnt/fat32/$KERNEL.img mnt/fat32/$KERNEL-backup.img
sudo cp arch/arm64/boot/Image mnt/fat32/$KERNEL.img
sudo cp arch/arm64/boot/dts/broadcom/*.dtb mnt/fat32/
sudo cp arch/arm64/boot/dts/overlays/*.dtb* mnt/fat32/overlays/
sudo cp arch/arm64/boot/dts/overlays/README mnt/fat32/overlays/
sudo umount mnt/fat32
sudo umount mnt/ext4
Another option is to copy the kernel into the same place but with a different filename – for instance, kernel-myconfig.img
– rather than overwriting the kernel.img
file. You can then edit the config.txt
file to select the kernel that the Raspberry Pi will boot:
kernel=kernel-myconfig.img
This has the advantage of keeping your custom kernel separate from the stock kernel image managed by the system and any automatic update tools and allowing you to quickly revert to a stock kernel if your kernel cannot boot.
Finally, plug the card into the Raspberry Pi and boot it!
Once Raspberry Pi boots, connect to it over SSH and run the ‘uname -r’ command to check the kernel release.
If you want to compile an upstream kernel rather than the Raspberry Pi Foundation’s downstream kernel, please see RPi Upstream Kernel Compilation for a few tips.
Configuring the Kernel
The Linux kernel is highly configurable; advanced users may wish to modify the default configuration to customise it to their needs, such as enabling a new or experimental network protocol or support for new hardware.
Configuration is most commonly done through the make menuconfig
interface. Alternatively, you can modify your .config
file manually, which can be more difficult for new users.
Preparing to Configure
The menuconfig
tool requires the ncurses
development headers to compile properly. These can be installed with the following command:
sudo apt install libncurses5-dev
Using menuconfig
Once you’ve got everything set up and ready to go, you can compile and run the menuconfig
utility as follows:
make menuconfig
If you’re cross-compiling a 32-bit kernel:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
Or, if you are cross-compiling a 64-bit kernel:
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- menuconfig
The menuconfig
utility has simple keyboard navigation. After a brief compilation, you’ll be presented with a list of submenus containing all the options you can configure; there’s a lot, so take your time to read through them and get acquainted.
Use the arrow keys to navigate, the Enter key to enter a submenu (indicated by +—>+), Escape twice to go up a level or exit, and the space bar to cycle the state of an option. Some options have multiple choices, so they’ll appear as a submenu, and the Enter key will select an option. You can press h on most entries to get help with that specific option or menu.
Resist the temptation to enable or disable many things on your first attempt; it’s relatively easy to break your configuration, so start small and get comfortable with the configuration and build process.
Saving your Changes
Once you’re done making the changes you want, press Escape until you’re prompted to save your new configuration. By default, this will save to the .config
file. You can save and load configurations by copying this file around.
Patching the Kernel
When building your custom kernel, you may wish to apply patches or collections of patches (‘patchsets’) to the Linux kernel.
Patchsets are often provided with newer hardware as a temporary measure before the patches are applied to the upstream Linux kernel (‘mainline’) and then propagated to the Raspberry Pi kernel sources. However, patchsets for other purposes exist, for instance, to enable a fully pre-emptive kernel for real-time usage.
Version Identification
It’s essential to check what kernel version you have when downloading and applying patches. In a kernel source directory, running head Makefile -n 3
will show you the version the sources relate to:
VERSION = 6
PATCHLEVEL = 1
SUBLEVEL = 38
In this instance, the sources are for a 6.1.38 kernel. You can see what version you’re running on your system with the uname -r
command.
Applying Patches
How you apply patches depends on the format in which the patches are available. Most patches are a single file and applied with the patch
utility. For example, let’s download and patch our example kernel version with the real-time kernel patches:
wget https://www.kernel.org/pub/linux/kernel/projects/rt/6.1/patch-6.1.38-rt13-rc1.patch.gz
gunzip patch-6.1.38-rt13-rc1.patch.gz
cat patch-6.1.38-rt13-rc1.patch | patch -p1
Some patchsets come as mailbox-format patchsets arranged as a folder of patch files. We can use Git to apply these patches to our kernel, but first, we must configure Git to let it know who we are when we make these changes:
git config --global user.name "Your name"
git config --global user.email "your email in here"
Once we’ve done this, we can apply the patches:
git am -3 /path/to/patches/*
Some patchsets will require a specific commit to patch against; follow the details provided by the patch distributor.
Some useful tips
- Using the command line is mandatory for many operations needed for embedded Linux development. It is a very powerful way of interacting with the system, with which we can save a lot of time.
- We can use several tabs in the Terminal
- Remember that you can use relative paths (for example:
../../linux
) in addition to absolute paths (for example:/home/user
) - In a shell, hit
[Control] [r]
, then a keyword, which will search through the command history. Hit[Control] [r]
again to search backward in the history. - The Linux kernel contains extensive knowledge on interfacing with hardware devices in ways known to work due to the ability for thousands of users to provide feedback and updates to the code. Look at the version control history for some of the interface software.
About the Author
Kurva Prashanth wrote this article with only light editing by Peter Dalmaris.
Kurva is good at Embedded GNU/Linux, contributing to FOSS projects (Real-Time OS, Custom distros for embedded and PC, Packaging and Tooling) and making technology accessible to everyone. He is interested in equitable education and excited about STEM subjects to work in agriculture through robots and sensor applications.