Compiling the Kernel

Choosing a Kernel

Choosing a suittable kernel can sometimes be difficult, since there is so much to choose from. The first step is reflecting the requirements:

  • Do I want Long Term Support?

  • Do I need vendor-specific-additions? In any case, the preferrable method is choosing the newest version of the kernel that has been chosen.

In this example, we are using version 5.4.50 of the mainline kernel.

Licensing

The linux Kernel is under GPLv2 License. That means you have to make the source code of your kernel available in one of the ways specified in the license! (see COPYING file in kernel repo)

Building

Getting the Kernel

When the right kernel is chosen, it first has to be fetched and unpacked:

wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.4.50.tar.xz
tar xf linux-5.4.50.tar.xz
mv linux-5.4.50 linux-stable

There are over 57,000 files in the 5.4 kernel containing C source code, header files, and assembly code, amounting to a total of over 14 million lines of code. Nevertheless, it is worth knowing the basic layout of the code and to know where to look for a particular component. The main directories of interest are the following:

  • arch: Contains architecture-specific files. There is one subdirectory per architecture.

  • Documentation: Contains kernel documentation.

  • drivers: Contains device drivers. There is a subdirectory for each type of driver.

  • fs: Contains filesystem code.

  • include: Contains kernel header files, including those required when building the toolchain.

  • init: Contains the kernel startup code.

  • kernel: Contains core functions, including scheduling, locking, timers, power management, and debug/trace code.

  • mm: Contains memory management.

  • net: Contains network protocols.

  • scripts: Contains many useful scripts, including the device tree compiler (DTC) which I described in Chapter 3, All About Bootloaders.

  • tools: Contains many useful tools, including the Linux performance counters tool, perf, which I will describe in Chapter 20, Profiling and Tracing.

Kernel Configuration (Kconfig)

Configuring the Kernel is one of the strengths of Linux. The degree to which the Kernel can be configured is huge! The Kconfig can be created by using Kbuild, a build system for the Linux Kernel used by a lot of projects (U-Boot, Crosstool-NG, …)

In Linux, the top-level Kconfig looks like this:

mainmenu "Linux/$(ARCH) $(KERNELVERSION) Kernel Configuration"
comment "Compiler: $(CC_VERSION_TEXT)"
source "scripts/Kconfig.include"
[]

And the first line of arch/Kconfig is this:

source "arch/$(SRCARCH)/Kconfig"

This line sources the configuration file (architecture dependant) that sources other Kconfig files.

Defconfig

Since menuconfigs can be used to create defconfig files, you can also use those to configure the Kernel without the user interface popping up everytime. There are also a lot of prebuilt configuration files stored in arch/$ARCH/configs. E.g.:

make ARCH=arm multi_v7_defconfig

Compiling - Kbuild

Kbuild is a set of Makefiles and scripts that take their configuration from the .config file, resolve the dependencies and and compile everything needed for the Kernel image production. This kernel image contains all the statically linked components, possibly a device tree binary, and possibly one or more kernel modules.

Target

To know how to build the kernel image, the expectation of the bootloader has to be researched first. Here are a few examples:

  • U-Boot: U-Boot has required uImage, but newer versions can load a zImage file using the bootz command.

  • x86 targets: Requires a bzImage file.

  • Most other bootloaders: Require a zImage file.

Building a zImage file can be achieved like this:

make -j 4 ARCH=arm CROSS_COMPILE=arm-cortex_a8-linux-gnueabihf- zImage

The -j option configures the number of parallel running threads. Since this reduces the time taken for the Kernel build, this option should be maxed out with the number of processors in your system if you want to achieve maximum speed.

Note: The uImage format is not compatible with multi-platform images!

When building in the uImage format, the Kernel load address is essential, since it can vary from device to device. E.g.:

make -j 4 ARCH=arm CROSS_COMPILE=arm-cortex_a8-linux-gnueabihf- LOADADDR=0x80008000 uImage

Build artifacts

A kernel build generates two files in the top-level directory: vmlinux and System.map. vmlinux is the Kernel image as an ELF binary. By default it is called vmlinuz and is a compressed image file. It is being decompressed during runtime. When building for arm, the name changes to vmlinux and it is installed uncompressed, so it doesn’t have to be decompressed during runtime.

System.map contains the symbol table in a human-readable form.

Compiling Device Trees

The Kernel repository also contains a devicetree compiler under arch/$ARCH/boot/dts and the build script at arch/$ARCH/boot/dts/Makefile. The Makefile uses devicetree scripts located in that directory. E.g.:

make ARCH=arm dtbs
[]
  DTC     arch/arm/boot/dts/alpine-db.dtb
  DTC     arch/arm/boot/dts/artpec6-devboard.dtb
  DTC     arch/arm/boot/dts/at91-kizbox2.dtb
  DTC     arch/arm/boot/dts/at91-nattis-2-natte-2.dtb
  DTC     arch/arm/boot/dts/at91-sama5d27_som1_ek.dtb
[]

Compiling Kernel Modules

Kernel modules can be seperately built by executing the same Makefile with the modules target. But then the compiled modules are scattered all over the source files, thats why we provide an installation output path:

make -j4 ARCH=arm CROSS_COMPILE=arm-cortex_a8-linux-gnueabihf- \
    INSTALL_MOD_PATH=$HOME/rootfs modules_install

Kernel modules are put into the /lib/modules/[kernel version] directory, relative to the root of the filesystem.

Cleaning Kernel Sources

There are three make targets for cleaning the kernel source tree:

  • clean

  • mrproper (Return to source tree, delete everything)

  • distclean (like mrproper but also cleans editor backups, patch files, …)

Building a 64-bit Kernel for the RaspberryPi 4

First, we need to install the toolchain. This time, we use a prebuilt version:

cd ~
wget https://developer.arm.com/-/media/Files/downloads/gnu-a/10.2-2020.11/binrel/gcc-arm-10.2-2020.11-x86_64-aarch64-none-linux-gnu.tar.xz
tar xf gcc-arm-10.2-2020.11-x86_64-aarch64-none-linux-gnu.tar.xz
mv gcc-arm-10.2-2020.11-x86_64-aarch64-none-linux-gnu \
    gcc-arm-aarch64-none-linux-gnu

Next up, we install a couple of packages needed to fetch and build the kernel:

sudo apt install subversion libssl-dev

Then we clone and setup the kernel repository (here: Kernel version 4.19.y)

git clone --depth=1 -b rpi-4.19.y https://github.com/raspberrypi/linux.git
svn export https://github.com/raspberrypi/firmware/trunk/boot
rm boot/kernel*
rm boot/*.dtb
rm boot/overlays/*.dtbo

After that, we navigate to the new linux dir and build the kernel:

PATH=~/gcc-arm-aarch64-none-linux-gnu/bin/:$PATH
cd linux
make ARCH=arm64 CROSS_COMPILE=aarch64-none-linux-gnu- \ bcm2711_defconfig
make -j4 ARCH=arm64 CROSS_COMPILE=aarch64-none-linux-gnu-

And last but not least we copy the built kernel image, device tree blops, etc. to the boot subdirectory we are using later on:

cp arch/arm64/boot/Image ../boot/kernel8.img
cp arch/arm64/boot/dts/overlays/*.dtbo ../boot/overlays/
cp arch/arm64/boot/dts/broadcom/*.dtb ../boot/
cat << EOF > ../boot/config.txt
enable_uart=1
arm_64bit=1
EOF
cat << EOF > ../boot/cmdline.txt
console=serial0,115200 console=tty1 root=/dev/mmcblk0p2 rootwait
EOF

And voilà! We have ourselves a ready-to-go RaspberryPi 4 compatible Kernel!

source: Mastering Embedded Linux Programming - Third Edition By Frank Vasquez, Chris Simmonds