Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 3

Custom Linux Kernel Compilation

Recommended resources

Introduction

Welcome back. In our previous lesson, we mapped the logical subsystems of the Linux kernel to their physical locations within the source code tree. You learned how to navigate this vast codebase and used tools like grep and find to explore the block/ subsystem, just as a kernel developer would when learning a new area of the code.

Today, we move from navigating the code to building it. The learning outcome for this lesson is to configure and compile a custom Linux kernel from source with specific features enabled or disabled. This is a fundamental skill that underpins almost all other kernel development activities, from bug fixing and feature development to performance tuning and security hardening. Mastering this process is a critical step toward contributing to open-source projects.

We will cover:

  1. The core components of the kernel's build system: Kconfig and Kbuild.
  2. A step-by-step guide to configuring, compiling, and installing a custom kernel on your host machine.
  3. A scenario-based look at how this process adapts for cross-compilation, a common requirement in embedded systems development.

1. The Kernel Build System: Kconfig and Kbuild

Before we start issuing make commands, it's important to understand the sophisticated system that manages the build process for a project as large and configurable as the Linux kernel. This system is primarily composed of two parts: Kconfig and Kbuild.

  • Kconfig: This is the configuration system. Its job is to manage the thousands of configuration options available in the kernel. It uses a special language across many Kconfig files in the source tree to define options, their types (boolean, tristate, string, etc.), and their dependencies. When you run a tool like make menuconfig, you are interacting with a frontend to the Kconfig system. Its ultimate output is the .config file, which is a simple text file that lists every configuration option and its selected value (e.g., CONFIG_SMP=y).

  • Kbuild: This is the build system itself. It is an elaborate collection of Makefiles that recursively traverse the source tree. Kbuild's job is to read the .config file and, based on the options you've selected, compile only the necessary source files and link them together to produce the final kernel image (vmlinux) and any loadable modules.

To get a better sense of how these systems work, please review the following article.

Exploring the Linux kernel: The secrets of Kconfig/kbuild

The article 'Exploring the Linux kernel: The secrets of Kconfig/kbuild' from opensource.com provides a concise yet detailed overview of the kernel's build infrastructure.

Read the sections titled 'Kconfig' and 'kbuild'. Focus on understanding: The role of the various make *config targets (menuconfig, defconfig, etc.). How the Kconfig system processes Kconfig files and an existing .config to generate the final configuration. The concept of 'recursive make' used by Kbuild. How Kbuild uses built-in.a files from different subdirectories to assemble the final vmlinux file. The 'vmlinux overview' diagram is particularly insightful.

The diagram below, taken from the article, visualizes how Kbuild combines components from across the source tree—many of which you identified in our last lesson—into the final uncompressed kernel executable, vmlinux.

Vmlinux Generation Process
This diagram illustrates how compiled object files from various kernel subsystems (init, kernel, mm, fs, drivers, etc.) are archived into `built-in.a` files and then linked together by the Kbuild system to create the final `vmlinux` file.

2. A Step-by-Step Guide to Building a Custom Kernel

Now, let's walk through the practical process of compiling a kernel for your host machine. We will use a combination of a detailed video walkthrough and a text-based guide for easy reference.

The process can be broken down into four main stages:

  1. Install Prerequisites
  2. Configure the Kernel
  3. Compile the Kernel
  4. Install the Kernel and Modules

Linux Kernel Programming 01: Compile and Boot

This video from the 'Write your own Operating System' YouTube channel provides a comprehensive, practical demonstration of the entire kernel compilation process, from downloading the source to booting the new kernel.

I recommend watching this video from start to finish (07:04 - 42:44) to see the full, end-to-end workflow. We will break it down into specific steps below, but seeing it all together is very valuable. Pay close attention to the reasoning behind each command.

For a supplementary text-based guide with commands you can easily copy, you can refer to the OneUptime blog post, "How to Compile a Custom Linux Kernel on Ubuntu" (ID: LINK).

Step 2.1: Install Prerequisites

Before you can build the kernel, you need a C compiler (GCC), make, and several other development libraries and tools.

  • Action: Install the necessary build dependencies. The video segment "Install required packages" (07:04 - 12:18) and Section "Installing Build Dependencies" in the OneUptime article (LINK, section 0) both cover this. A typical command on a Debian/Ubuntu system would be:
    sudo apt update
    sudo apt install build-essential libncurses-dev bison flex libssl-dev libelf-dev
    

Step 2.2: Configure the Kernel

This is the most critical step for customization. Your goal is to create a .config file that reflects the desired features for your new kernel.

  • Best Practice: Never start from scratch. The safest and most efficient method is to copy the configuration file from your currently running, working kernel. This ensures that all the necessary drivers for your hardware are already enabled.
    
    
    
    # Navigate to your kernel source directory
    cd linux
    
    
    
    # Copy the config from your running kernel
    cp /boot/config-$(uname -r) .config
    
  • Update the Configuration: If you are building a newer kernel version than the one you are running, your copied .config will be missing entries for new features. You can update it automatically.
    • make oldconfig: This will prompt you for every new option.
    • make olddefconfig: This is non-interactive and will automatically select the default value for all new options. This is usually what you want.
  • Customize: Now you can enable or disable specific features using a configuration tool. The most common is make menuconfig, a text-based user interface.
Linux Kernel 6.0.7 `menuconfig` Interface
The main menu of the `menuconfig` interface, showing major configuration categories. You can navigate this menu to enable ([*]), disable ([ ]), or build features as modules ([M]).
  • Action:
    1. Copy your existing kernel configuration.
    2. Run make olddefconfig to update it for the new kernel version.
    3. Launch make menuconfig.
    4. Practice: Navigate to File systems and find the entry for a filesystem you don't use (e.g., Btrfs, JFS, XFS). If it's enabled (* or M), disable it by pressing n.
    5. Save your changes and exit.

The video segment "The .config file" (12:34 - 21:47) provides an excellent walkthrough of this entire configuration process.

Step 2.3: Compile the Kernel

With your .config file ready, you can start the compilation. This is CPU-intensive and can take a significant amount of time.

  • Action: Use the make command with the -j flag to run multiple compilation jobs in parallel. A good practice is to use the number of CPU cores available on your system.
    
    
    
    # This command builds the kernel image and all modules
    make -j$(nproc)
    

This process is shown in the video segment starting at 23:45. The main outputs will be:

  • vmlinux: The uncompressed, statically linked kernel executable in the root of the source tree.
  • arch/<your_arch>/boot/bzImage (for x86) or Image (for ARM): The compressed, bootable kernel image.

Step 2.4: Install the Kernel and Modules

After the compilation succeeds, you need to install the newly built kernel and its modules onto your system.

  • Action: This is a two-step process requiring root privileges.
    1. Install Modules: This command copies all the compiled modules (files ending in .ko) to the directory /lib/modules/<your-new-kernel-version>/.
      sudo make modules_install
      
    2. Install Kernel: This command copies the compressed kernel image to /boot/, creates the initial RAM disk (initrd), and—most importantly—updates your bootloader (GRUB) configuration to add an entry for the new kernel.
      sudo make install
      

After these commands complete, you can reboot your machine. Your bootloader should now present an option to boot into your custom-built kernel. You can verify it's running with uname -r.

The video demonstrates the installation and GRUB update process from 28:54 to 33:02.


3. Scenario: Cross-compilation for an Embedded Target

As an embedded engineer, you will most often be cross-compiling—building the kernel on your powerful x86 host machine for a different target architecture, like ARM or RISC-V. The fundamental process is the same, but with two key differences: you need a cross-compiling toolchain, and you must explicitly tell Kbuild which architecture and toolchain to use.

The following video demonstrates how to build a kernel for an ARM target and run it in QEMU—a workflow highly relevant to your goals.

QEMU for Linux kernel developers

This video, 'QEMU for Linux kernel developers', walks through the process of cross-compiling an ARM kernel and booting it in an emulator.

Watch the section on compiling the Linux kernel (03:23 - 04:44). As you watch, identify the answers to these questions: How are the target architecture and the cross-compiler specified in the make command? What defconfig is used as the starting configuration, and why is it different from the cp /boot/config... method we used for a native build? After the build is complete, in which subdirectory is the final zImage (compressed ARM kernel) located?

The key insight from this exercise is the use of the ARCH and CROSS_COMPILE environment variables passed to make:
ARCH=arm CROSS_COMPILE=<path-to-toolchain>/bin/arm-none-linux-gnueabihf- make ...

This tells Kbuild to use the ARM architecture's defconfig and Makefiles (arch/arm/) and to use the specified ARM cross-compiler instead of the host's native GCC.


Conclusion

In this lesson, you have acquired the essential practical skill of building a custom Linux kernel. You now understand the roles of the Kconfig and Kbuild systems and have walked through the complete workflow of configuration, compilation, and installation.

Key Takeaways:

  • The kernel build is managed by Kconfig (for configuration) and Kbuild (for compilation).
  • The best practice for configuration is to start with your current system's .config file and modify it using make menuconfig.
  • The three main commands for building and installing are make -j$(nproc), sudo make modules_install, and sudo make install.
  • Cross-compilation follows the same principles but requires specifying the ARCH and CROSS_COMPILE variables.

In our next lesson, we will leverage the kernel we've just built. We will begin to describe the major stages of the Linux boot process, tracing the journey from the moment the bootloader hands control to our kernel's entry point, through architecture-specific setup, and into the generic start_kernel function.