Cross-Compiling Embedded Linux Kernel: A Step-by-Step Guide

Cross-Compiling Embedded Linux Kernel: A Step-by-Step Guide

Hello everyone, I am the Intelligence Guy~

Recently, I have been compiling the Linux kernel and have accumulated some summarizing experiences, which I would like to share here.

Below are some commands I used to compile the kernel. Of course, the prerequisite is to set up the cross-compilation toolchain in the environment variables, ensuring that the correct cross-compiler is available in your <span>PATH</span> or explicitly specified through the <span>CROSS_COMPILE</span> variable (for example, <span>CROSS_COMPILE=aarch64-linux-gnu-</span>). This is usually the first step in setting up the environment, and then you can execute the following commands:

my@my-vm:~/my-stm32mp2-linux$ export OUTPUT_BUILD_DIR=../build
my@my-vm:~/my-stm32mp2-linux$ make ARCH=arm64 O="$PWD/../build" my_stm32mp257x_defconfig

// Compile the kernel and device tree
my@my-vm:~/my-stm32mp2-linux$ make ARCH=arm64 Image vmlinux dtbs LOADADDR=0xC2000040 O="$PWD/../build" -j10

// Compile kernel modules
my@my-vm:~/my-stm32mp2-linux$ make ARCH=arm64 modules O="${OUTPUT_BUILD_DIR}" -j10
my@my-vm:~/my-stm32mp2-linux$ make ARCH=arm64 INSTALL_MOD_PATH="$PWD/../build/install_artifact" \
modules_install O="$PWD/../build" -j10

Basically, the cross-compilation process for the current embedded Linux kernel is quite similar, so there is no need to be afraid. The above commands are mainly used to build the kernel for ARM64 (AArch64) SoCs like the STM32MP257x.

Now, let me explain these commands in detail:

<span><span>export OUTPUT_BUILD_DIR=../build</span></span>

The main purpose of this command is to set an environment variable <span>OUTPUT_BUILD_DIR</span>, with the value of <span>../build</span> (the build folder in the parent directory of the current directory <span>~/my-stm32mp2-linux</span>). This variable is used to specify where all output files generated during the kernel build process (such as <span>.o</span> files, configuration files, final images, etc.) should be stored.

The benefits of doing this are numerous:

1. Separation of source code and build output: Keeps the source code directory (<span>~/my-stm32mp2-linux</span>) clean, containing only the original code.

2. Support for multiple build configurations: Allows for different target boards, configurations, or branches to use different output directories (such as <span>../build-mp257x-test</span>, <span>../build-mp257x-prod</span>) under the same source code.

3. Simplification of commands: Subsequent commands can use <span>$OUTPUT_BUILD_DIR</span> instead of the lengthy <span>"$PWD/../build"</span> path.

<span><span>make ARCH=arm64 O="$PWD/../build" my_stm32mp257x_defconfig</span></span>

This command is mainly used to configure the kernel, where:

<span>ARCH=arm64</span>: Specifies the target architecture as ARM64 (AArch64). This is necessary, as your host machine (<span>my-vm</span>) is likely x86/x86_64 architecture, which I won’t elaborate on here~

<span>O="$PWD/../build"</span>: Specifies the build output directory (Out-of-tree build directory). <span>$PWD</span> represents the current working directory (<span>~/my-stm32mp2-linux</span>), so <span>"$PWD/../build"</span> is the <span>../build</span> defined in the previous step. Make will use this directory as the “working directory” for the build, where all temporary files and final outputs are placed, keeping it clean, especially when I sometimes read the source code. It is consistent with the <span>$OUTPUT_BUILD_DIR</span> from the first step.

<span>my_stm32mp257x_defconfig</span>: This is a kernel configuration target. It specifies which predefined configuration file to use to initialize the kernel’s <span>.config</span> file. Typically, SoC vendors provide corresponding <span>defconfig</span> files for specific boards (like STM32MP257x-DK) in the <span>arch/arm64/configs/</span><code><span> directory.</span>

Executing this command will generate a <span>.config</span> file in the <span>$O</span> (<span>../build</span>) directory, which contains the kernel options (drivers, subsystem support, etc.) enabled for the <span>my_stm32mp257x</span> board and their default configurations. This serves as the basis for subsequent compilation.

<span><span>make ARCH=arm64 Image vmlinux dtbs LOADADDR=0xC2000040 O="$PWD/../build" -j10</span></span>

This command is mainly for compiling the kernel image and device tree binary files (Device Tree Blobs)..

Other parts are quite similar, but the key points are:

1)<span>Image</span>: This is a key target for ARM64 architecture. It compiles and generates an uncompressed kernel image file suitable for U-Boot’s <span>booti</span> command (usually located at <span>$O/arch/arm64/boot/Image</span>).

2)<span>vmlinux</span>: This is the compiled ELF format executable kernel file (uncompressed and unbundled), containing all symbol information. It is mainly used for debugging (e.g., with <span>gdb</span>), and the file is large (located at <span>$O/vmlinux</span>). Typically, in actual deployment, the <span>Image</span> (or compressed version <span>Image.gz</span>) is used instead of <span>vmlinux</span>.

3)<span>dtbs</span>: The compilation target for Device Tree Blobs. The device tree (<span>.dts</span> source files) describes the hardware information of specific development boards (memory layout, peripherals, GPIO, clocks, etc.).<span>dtbs</span> command compiles the <span>.dts</span> files into a binary format <span>.dtb</span> file that can be recognized by bootloaders (like U-Boot) and the kernel.

4)<span>LOADADDR=0xC2000040</span>: This value specifies the expected loading address of the kernel image <span>Image</span> in the target board’s physical memory (<span>0xC2000040</span>). This address must be consistent with your bootloader (U-Boot) settings and the board’s memory layout.

For the STM32MP25x platform, <span>0xC2000040</span> is a typical loading address for the kernel image. This value originates from the ARM Image Header specification (usually TEXT_OFFSET = 0x40) and platform-specific memory mapping. When compiling other similar kernels, this value is generally different, so you need to refer to the target board documentation or the configuration provided by the vendor.

Similarly, <span>O="$PWD/../build"</span>: Specifies the build output directory, which must be consistent with the directory used in the configuration step (<span>defconfig</span>), otherwise Make will not find the <span>.config</span> file.

<span>-j10</span>: Enables parallel compilation, using 10 worker threads (jobs). The number <span>10</span> is typically set to 1-2 times the number of CPU cores on the host machine (depending on memory and I/O speed) to speed up the compilation (e.g., <span>-j$(nproc)</span><span>).</span>

<span><span>make ARCH=arm64 modules O="${OUTPUT_BUILD_DIR}" -j10</span></span> Finally, this command is basically for compiling loadable kernel modules. Mainly used in the file system:

<span>modules</span>: Compilation target. Components marked as <span>M</span> (Module) in the kernel configuration (most drivers, certain file systems, protocol stacks, etc.) will be compiled into <span>.ko</span> (Kernel Object) files. These modules can be dynamically loaded and unloaded after the kernel starts.

Similarly, <span>O="${OUTPUT_BUILD_DIR}"</span>: Specifies the build output directory. Here, the environment variable <span>OUTPUT_BUILD_DIR</span> defined in the first step is used, which has the value of <span>../build</span>. It is essential to ensure that the same output directory is used as in the previous steps.

<span><span>make ARCH=arm64 INSTALL_MOD_PATH="$PWD/../build/install_artifact" modules_install O="$PWD/../build" -j10</span></span> This is the final step to install the compiled kernel modules into the specified directory structure.

<span>INSTALL_MOD_PATH="$PWD/../build/install_artifact"</span>: Specifies the base path for module installation.

<span>$PWD/../build/install_artifact</span>: This means installing in a subdirectory named <span>install_artifact</span> under the output directory (<span>../build</span>). After executing this command, <span>make</span> will copy the compiled <span>.ko</span> files from the <span>$O</span> directory to the path specified by <span>INSTALL_MOD_PATH</span>, following the standard directory structure for kernel modules (mainly <span>lib/modules/<kernel_version></span>). The final installation path will be:<span>$PWD/../build/install_artifact/lib/modules/<kernel_version>/kernel/...</span>

<span>modules_install</span>: Installation target.<span>O="$PWD/../build"</span>, tells <span>make</span> where to find the compiled <span>.ko</span> files and kernel version information).

Finally

I have collected some embedded learning materials. Reply with 1024 in the public account to find the download link!

Recommended good articles, click the blue text to jump
☞ Collection | Comprehensive Linux Application Programming
☞ Collection | Learn Some Networking Knowledge
☞ Collection | Handwritten C Language

☞ Collection | Handwritten C++ Language
☞ Collection | Experience Sharing
☞ Collection | From Microcontrollers to Linux
☞ Collection | Power Control Technology
☞ Collection | Essential Mathematics for Embedded Systems
☞ Collection | MCU Advanced Collection

Leave a Comment