Cross-Compiling the Linux Kernel for ARM-Free Linux kernel development course

 

Previous Lecture
Next Lecture

 

Cross-Compiling the Linux Kernel for ARM
Chapter 3 · Linux Kernel Programming Series · EmbeddedPathashala
Updated for Linux Kernel 6.x  |  Raspberry Pi & ARM Targets
⚙️
Topic
Cross-Compilation
🎯
Level
Beginner–Intermediate
📦
Kernel
6.x (LTS)
🆓
Course
100% Free

Topics Covered in This Tutorial

Cross Compilation
ARM Toolchain
ARCH Variable
CROSS_COMPILE
defconfig
zImage / Image
DTB Files
Kernel Modules
Raspberry Pi Build
Kernel 6.x Updates

What is Cross-Compilation?

When you write a C program on your laptop and compile it, the resulting binary runs on your laptop. That is native compilation — the machine that builds the code is the same machine that runs it.

Cross-compilation is different. You build the code on one machine (say, your x86-64 laptop), but the resulting binary runs on a completely different processor architecture — like an ARM-based Raspberry Pi or an embedded board. The machine doing the building is called the host. The machine that will run the code is called the target.

Why bother? Because embedded targets (Raspberry Pi, STM32, BeagleBone, etc.) are often slow or resource-constrained. Compiling a full Linux kernel directly on a Raspberry Pi would take a very long time. On your powerful x86 laptop, the same job finishes in minutes.

Cross-Compilation: Host vs Target
💻
HOST
x86-64 Linux PC
(Ubuntu / Debian)
Runs the compiler
arm-linux-gnueabihf-gcc
produces ARM binary
🍓
TARGET
ARM Board
(Raspberry Pi, etc.)
Runs the kernel

Step 1: Setting Up the Cross-Compilation Toolchain

Before you can build the kernel for ARM, you need a special compiler — one that runs on your x86 machine but produces code for an ARM processor. This is called a cross-compiler toolchain.

A toolchain is not just one tool. It is a collection of programs that work together: the C compiler (gcc), the linker (ld), the assembler (as), the object dump utility (objdump), and several others. In a cross toolchain, all of these produce output for your target architecture — not your host.

Method 1 — Quick Install via apt (Recommended)

The simplest way to get a working ARM cross-compiler on Ubuntu or Debian is through the package manager. For ARM 32-bit targets (like Raspberry Pi running a 32-bit OS):

sudo apt install crossbuild-essential-armhf

This installs the complete ARM hard-float toolchain. The tools land in /usr/bin/, which is already in your PATH — so you can use them immediately.

To verify the installation, check where the compiler lives and what version it is:

which arm-linux-gnueabihf-gcc
arm-linux-gnueabihf-gcc --version | head -n1
🔵 ARM 32-bit vs ARM 64-bit: The crossbuild-essential-armhf package is for ARM 32-bit targets (AArch32). If your target is a 64-bit ARM board running a 64-bit OS (AArch64), install crossbuild-essential-arm64 instead. Raspberry Pi 4 and Pi 5 running 64-bit Raspberry Pi OS need the arm64 toolchain.

Breaking Down the Toolchain Prefix Name
arm
Target
Architecture
linux
Target
OS
gnueabi
C Library &
ABI Type
hf
Hard Float
FPU
Full prefix: arm-linux-gnueabihf- · Tools: arm-linux-gnueabihf-gcc, arm-linux-gnueabihf-ld, …

Method 2 — Building or Cloning a Toolchain from Source

In some situations — especially when you need a very specific GCC version or need to target a vendor-specific SoC — the packaged toolchain may not be enough. You can obtain pre-built toolchains from the ARM Developer website or build one using crosstool-NG.

When you use a toolchain that is not in your system’s default PATH, you have to tell your shell where to find it. You do this by updating the PATH variable:

# Example: adding a custom toolchain directory to PATH
export PATH=/opt/arm-toolchain/bin:$PATH
⚠️ Make it permanent: The export PATH=... command only affects your current terminal session. Add it to your ~/.bashrc or ~/.profile to make it permanent across reboots and new terminal windows.
🔵 Kernel 6.x Update — Toolchain Requirements: Linux kernel 6.x requires GCC 5.1 as the minimum. However, for best compatibility and performance, using GCC 12 or GCC 13 (available in Ubuntu 22.04/24.04) is recommended. The default toolchain from apt on modern Ubuntu is always a safe choice.

Step 2: Understanding ARCH and CROSS_COMPILE

The Linux kernel build system is controlled through its Makefile. When you cross-compile, you must pass two critical variables to make. These are not optional — without them, the kernel will compile for your host machine, not your target.

Variable What It Tells the Build System Example Values
ARCH The CPU architecture of the target machine arm, arm64, x86, riscv, powerpc
CROSS_COMPILE The prefix that all toolchain programs share arm-linux-gnueabihf-, aarch64-linux-gnu-

How ARCH Works

The value you set for ARCH must match a directory name inside the arch/ folder of the kernel source tree. Go into any Linux kernel source and run ls arch/ — you will see directories like arm, arm64, x86, riscv, mips, etc. Each one contains architecture-specific code for that platform.

arch/ Directory in Linux Kernel Source
linux-6.x/
  └── arch/
arm
arm64
x86
riscv
mips
powerpc
openrisc
… more
ARCH=arm → build system looks inside arch/arm/

How CROSS_COMPILE Works

CROSS_COMPILE is a string prefix. The kernel’s Makefile appends tool names to this prefix when it needs to invoke compiler tools. So if you set CROSS_COMPILE=arm-linux-gnueabihf-, the build system will call:

arm-linux-gnueabihf-gcc     # C compiler
arm-linux-gnueabihf-ld      # Linker
arm-linux-gnueabihf-objcopy # Object copy tool
arm-linux-gnueabihf-strip   # Strip tool

This is why you need the toolchain directory in your PATH — the Makefile calls these tools by name, and the shell needs to be able to find them.

🔵 Kernel 6.x: LLVM/Clang as an alternative: Starting with kernel 5.7, you can also build the Linux kernel using the LLVM/Clang toolchain. For Clang-based cross-compilation, you use LLVM=1 and CROSS_COMPILE= (pointing to LLVM tools). But for beginners, the GCC toolchain approach we cover here is simpler and more widely documented.

Step 3: Configuring the Kernel for Your Target

Before you can build, you need to configure the kernel — telling it which features to enable or disable. The Linux kernel has thousands of configurable options. Fortunately, you do not have to set all of them manually. You start from a default configuration that already works for your target board.

What is a defconfig?

A defconfig (default configuration) file is a pre-set collection of configuration options maintained by kernel developers for a specific board or SoC family. These files live inside arch/<ARCH>/configs/ in the kernel source.

When you run make <board>_defconfig, the build system reads the matching file from that directory and generates your .config file — which is the actual kernel configuration used during the build.

How defconfig Becomes Your .config
📄
arch/arm/configs/
bcm2709_defconfig
imx_v6_v7_defconfig
multi_v7_defconfig …
⚙️
make ARCH=arm
bcm2709_defconfig
🔧
linux-6.x/.config
Used for the build
(thousands of options)

Cleaning Before a Fresh Build

Before configuring a new kernel, it is good practice to clean out any leftover files from a previous build. The mrproper target does a thorough clean:

make mrproper

This removes all generated files including your .config. Use it when you are starting completely fresh or switching to a different target board.

Configuring for ARM 32-bit (Raspberry Pi 2 / 3 style)

# Set ARCH and CROSS_COMPILE for ARM 32-bit
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-

# Apply a board-appropriate defconfig
make bcm2709_defconfig

Configuring for ARM 64-bit (Raspberry Pi 4 / 5 and modern boards)

# Set ARCH and CROSS_COMPILE for ARM 64-bit
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-

# Apply the generic ARM64 defconfig (works for most boards)
make defconfig
🔵 Kernel 6.x Change: For Raspberry Pi 4 and Pi 5 running a 64-bit OS, use ARCH=arm64 and the aarch64-linux-gnu- toolchain. The older bcm2709_defconfig is for 32-bit ARM. Modern Pi boards ship with 64-bit Raspberry Pi OS by default, so arm64 is now the more common choice.

Optional: Fine-Tuning with menuconfig

After applying a defconfig, you can optionally open the interactive menu-based configurator to adjust individual settings. This is useful when you need to add a specific driver or tweak kernel parameters:

make ARCH=arm menuconfig

For most beginners, skipping this step and using the defconfig directly is perfectly fine.

Step 4: Building the Kernel, Modules, and DTBs

With configuration done, you are ready to actually compile. The make command takes the .config you generated and builds everything.

What Are DTBs?

A Device Tree Blob (DTB) is a file that describes the hardware layout of your board to the kernel — things like where the memory is, what peripherals are connected, which interrupt lines they use, and so on. ARM boards rely heavily on device tree because the hardware is not self-describing (unlike x86 PCI). Without the correct DTB, the kernel cannot talk to your board’s hardware correctly.

Building for ARM 32-bit

make -j$(nproc) ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zImage modules dtbs

Building for ARM 64-bit (Kernel 6.x — modern boards)

make -j$(nproc) ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- Image modules dtbs
🔵 zImage vs Image: For ARM 32-bit, the compressed kernel image is called zImage. For ARM 64-bit, the image format is called Image (uncompressed) or Image.gz (compressed). This is a key difference from the older kernel documentation.
🔵 -j$(nproc) explained: The -j flag tells make how many parallel jobs to use. $(nproc) automatically picks the number of CPU cores on your machine. If your PC has 8 cores, it runs 8 compile jobs in parallel — significantly reducing build time. You can also write it as -j8 manually.

What the Build Produces
🗜️
zImage / Image
The compressed kernel binary that the bootloader loads into RAM
arch/arm/boot/zImage
📦
Kernel Modules (.ko)
Loadable drivers and subsystems that extend the kernel at runtime
Scattered across source tree
🗺️
DTB Files
Hardware description blobs — tells the kernel what is on the board
arch/arm/boot/dts/*.dtb
🗒️
System.map
Symbol table — maps kernel function names to their memory addresses
System.map

Step 5: Deploying the Kernel to Your Target

Building the kernel is only half the job. You also need to get it onto your target device. The general process involves copying the kernel image, the DTB files, and the kernel modules to the right locations on your target’s storage.

Kernel Deployment Flow
Host: Build Complete
1. Copy Kernel Image
→ /boot partition
2. Install Modules
→ /lib/modules/ (rootfs)
3. Copy DTB Files
→ /boot partition
Boot Target Board

Installing Kernel Modules

Kernel modules must be installed into the target’s root filesystem, not your host’s. This is critically important. If you install modules without specifying a path, they will overwrite your host system’s modules — which can break your host machine.

Always use the INSTALL_MOD_PATH variable to point to the target’s root filesystem mount point:

# INSTALL_MOD_PATH points to where your target's root filesystem is mounted
sudo make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- \
     INSTALL_MOD_PATH=/mnt/target-rootfs \
     modules_install
⚠️ Critical: Never run make modules_install without INSTALL_MOD_PATH. It defaults to your host system’s root — this could overwrite /lib/modules/ on your own machine and break your running OS.

Copying DTB Files

# Copy all DTB files for your platform to the target's boot partition
sudo cp arch/arm/boot/dts/*.dtb /mnt/target-boot/

# Copy device tree overlays as well
sudo cp arch/arm/boot/dts/overlays/*.dtb* /mnt/target-boot/overlays/
🔵 Always sync: After copying files to a mounted SD card or USB drive, run sync to flush all write buffers to disk before unmounting. Otherwise you risk a corrupted filesystem.
sync

Quick Reference: Commands at a Glance
Action Command (ARM 32-bit) Command (ARM 64-bit)
Install toolchain sudo apt install crossbuild-essential-armhf sudo apt install crossbuild-essential-arm64
Clean source tree make mrproper
Apply defconfig make ARCH=arm bcm2709_defconfig make ARCH=arm64 defconfig
menuconfig make ARCH=arm menuconfig make ARCH=arm64 menuconfig
Build kernel + modules + dtbs make -j$(nproc) ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zImage modules dtbs make -j$(nproc) ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- Image modules dtbs
Install modules make INSTALL_MOD_PATH=/mnt/rootfs modules_install

Interview Questions — Cross-Compilation & Kernel Build

These questions are commonly asked in embedded Linux / kernel development interviews.

Q1. What is cross-compilation and why is it needed in embedded Linux development?
Cross-compilation means building software on one machine (the host) for a different machine (the target). In embedded development, the target board is typically too slow or too resource-constrained to compile a full Linux kernel by itself. By building on a powerful x86 PC and running the output on an ARM board, you get much faster build times and a smoother development workflow.
Q2. What do the ARCH and CROSS_COMPILE variables control in the Linux kernel build system?
ARCH tells the build system which CPU architecture to target. Its value must match a directory name under arch/ in the kernel source — for example, arm, arm64, or riscv. CROSS_COMPILE sets the toolchain prefix. The Makefile appends tool names to this prefix (like gcc, ld, objcopy) to call the correct cross-compilation utilities.
Q3. What is a defconfig and where does the kernel store these files?
A defconfig is a pre-configured set of kernel build options tailored for a specific board or SoC family. These files are stored inside arch/<ARCH>/configs/ in the kernel source tree. Running make <name>_defconfig reads that file and generates the .config that controls the actual build.
Q4. What is the difference between zImage and Image in the Linux kernel?
zImage is a self-decompressing compressed kernel image used for ARM 32-bit targets. Image is an uncompressed kernel binary used for ARM 64-bit (AArch64) targets. The difference is architecture-specific — most bootloaders for 64-bit ARM expect the Image format.
Q5. What is a DTB file and why is it important for ARM-based Linux systems?
A Device Tree Blob (DTB) is a compiled binary file that describes the hardware layout of an embedded board — memory regions, peripherals, interrupt mappings, clock sources, and so on. ARM boards are not self-describing like x86 PCI devices, so the kernel depends on the DTB to know what hardware it is running on. Without the correct DTB, drivers cannot initialize hardware properly.
Q6. Why is it dangerous to run make modules_install without INSTALL_MOD_PATH?
Without INSTALL_MOD_PATH, the kernel build system defaults to installing modules into the host machine’s /lib/modules/ directory. This would overwrite the running host system’s kernel modules, potentially making the host unstable or unbootable. Always set INSTALL_MOD_PATH to the mount point of the target’s root filesystem.
Q7. What does the -j flag do in a make command, and how do you choose the right value?
The -j flag specifies the number of parallel jobs that make can run simultaneously. More jobs means faster builds on multi-core machines. A common rule is to use the number of available CPU cores. You can get this automatically with -j$(nproc), where nproc outputs the core count. Setting it too high can cause build failures on memory-constrained systems.
Q8. What is the difference between make clean and make mrproper?
make clean removes most generated files but preserves the .config and enough infrastructure to rebuild quickly. make mrproper is a deeper clean — it removes everything including .config, restoring the tree to a completely pristine state as if freshly unpacked. Use mrproper when switching between different target boards or starting a completely fresh build.

Continue Your Linux Kernel Journey

This tutorial is part of the free Linux Kernel Programming series at EmbeddedPathashala — your free resource for embedded systems and kernel development.

Previous Lecture
Next Lecture

Leave a Reply

Your email address will not be published. Required fields are marked *