L11: Configuring and Building the Linux Kernel


← Previous Lecture
Chapter 2 — Part 2 of 2
Next Lecture →

Configuring and Building the Linux Kernel

From raw source to a running kernel — step by step, concept by concept

⏱ ~25 min read
🛠 Hands-on Focused
🖥 Kernel 6.x Updated

🎯 What You Will Learn

This free Linux kernel development lecture covers the complete process of configuring and compiling the Linux kernel from source. By the end you will know:

  • What the kbuild system is and why it exists
  • The .config file — the heart of kernel configuration
  • Every major way to configure the kernel: menuconfig, xconfig, defconfig, oldconfig, localmodconfig
  • What CONFIG options actually look like and how they control what gets compiled
  • How to compile the kernel and install it on your machine
  • Cross-compilation basics for embedded targets such as ARM64 and RISC-V

📋 Prerequisites

Before starting this free Linux kernel programming lecture make sure you have:

  • Read Part 1 of this chapter (Linux Kernel Source Tree Layout)
  • Ubuntu 22.04 or 24.04 installed (native or VM)
  • Basic familiarity with the Linux terminal
  • The kernel source downloaded and extracted (covered in Part 1)

Keywords in This Lecture

Linux kernel configuration
kbuild system
menuconfig
free Linux kernel development course
.config file
cross-compilation ARM64
free embedded systems course
kernel modules
free Linux device drivers course

1. Why Linux Kernel Configuration Exists

Imagine you are packing a bag for a trip. If you are going camping, you pack a tent and a sleeping bag. If you are going to a business conference, you pack a suit and a laptop. You do not take everything you own on every trip.

The Linux kernel works the same way. The full kernel supports thousands of features — Bluetooth, WiFi, 60+ filesystem types, drivers for thousands of hardware devices, support for 20+ CPU families. But your specific machine does not need all of it. A server does not need WiFi drivers. An embedded router does not need desktop sound card drivers.

Kernel configuration is how you decide exactly what goes into your kernel. The result is a custom kernel that is smaller, faster, more secure, and purpose-built for your specific hardware.

💡 Three Benefits of Proper Kernel Configuration

  • Smaller and faster: No bloat from drivers you do not need. Smaller image, faster boot.
  • More secure: Fewer features means a smaller attack surface. Unused subsystems cannot be exploited.
  • Purpose-built: A kernel tuned exactly for your hardware and use case performs significantly better.

2. Understanding the kbuild System for Linux Kernel Configuration

The Linux kernel uses a build system called kbuild. If you have used make before to compile C programs, kbuild is built on top of make — but far more sophisticated.

How kbuild Processes a Linux Kernel Build

Step 1: Kconfig files –> define what OPTIONS EXIST
|
v
Step 2: menuconfig –> YOU CHOOSE which options to enable
|
v
Step 3: .config file –> YOUR CHOICES are saved here
|
v
Step 4: make –> COMPILATION begins using .config
|
v
Step 5: vmlinux/bzImage –> FINAL KERNEL BINARY is produced

The two key file types that kbuild works with are Kconfig files and Makefiles:

Kconfig Files — The Menu of Available Options

Every directory in the kernel has its own Kconfig file listing what features can be turned on or off in that directory. These files define what configuration options exist, what their descriptions are, and what their dependencies are. Think of them as the menu from which you order.

Makefiles — What to Compile Based on Your Choices

Each directory also has a Makefile that tells kbuild what source files to compile based on which CONFIG options are enabled. A typical line looks like:

Example Kconfig + Makefile Relationship

# In drivers/bluetooth/Kconfig:
config BT_HCIUART
tristate “HCI UART driver”
depends on BLUETOOTH# In drivers/bluetooth/Makefile:
obj-$(CONFIG_BT_HCIUART) += hci_uart.o# This means: compile hci_uart.c ONLY IF CONFIG_BT_HCIUART is set.
# If CONFIG_BT_HCIUART=y –> compiled into the kernel image
# If CONFIG_BT_HCIUART=m –> compiled as hci_uart.ko module
# If CONFIG_BT_HCIUART=n –> not compiled at all

3. The .config File — Heart of Linux Kernel Configuration

After you run any configuration tool, all your choices are saved in a single file at the root of the kernel source tree: .config (note the dot — it is a hidden file in Linux).

What the .config File Looks Like Inside

#
# Automatically generated file; DO NOT EDIT.
# Linux/x86_64 6.9.0 Kernel Configuration
#CONFIG_64BIT=y
CONFIG_X86_64=y# CONFIG_WIRELESS is not set <– DISABLED, will NOT be compiledCONFIG_BLUETOOTH=y <– compiled INTO the kernel (built-in)
CONFIG_BT_RFCOMM=m <– compiled as a MODULE (.ko file)
CONFIG_BT_BNEP=m

CONFIG_EXT4_FS=y <– ext4 filesystem: built in
CONFIG_BTRFS_FS=m <– btrfs: loadable module
# CONFIG_XFS_FS is not set <– XFS: disabled completely

Three States for Every CONFIG Option

y (Yes / Built-in): The feature is compiled directly into the kernel image. It is always available from the moment the kernel boots. Boot-critical features like the scheduler and memory management must be set to y.

m (Module): Compiled as a separate .ko file that can be loaded and unloaded at runtime with modprobe. Useful for drivers you might or might not need depending on the hardware present.

n (Not set / Disabled): Completely excluded. No code is compiled for this feature. Saves both kernel image size and runtime memory.

💡 Tip — Read Your Running Kernel’s Config

On your current Ubuntu install, the config used to build the running kernel is available here. Try it now:

Reading the Running Kernel’s Config

# View the full config for your current running kernel
cat /boot/config-$(uname -r) | less# Search for Bluetooth settings
cat /boot/config-$(uname -r) | grep CONFIG_BLUETOOTH# If CONFIG_IKCONFIG_PROC=y was set at build time, you can also use:
zcat /proc/config.gz | grep CONFIG_BLUETOOTH

4. Ways to Configure the Linux Kernel — All Methods Explained

The kernel offers several ways to create or modify the .config file. Here are the most important ones for this free Linux kernel development course:

make menuconfig — Most Commonly Used Method

Opens a text-based, menu-driven configuration interface directly in the terminal. Works over SSH, requires no graphical display. This is what most embedded developers and students in any free Linux kernel programming course use because it runs anywhere — on a headless server, inside Docker, or over a remote SSH session.

menuconfig Terminal Interface (Text Representation)

+——- Linux Kernel Configuration ——————————–+
| Arrow keys navigate the menu. <Enter> selects submenus. |
| [*] built-in [ ] excluded <M> module capable |
+——————————————————————–+
| General setup —> |
| [*] 64-bit kernel |
| Processor type and features —> |
| Networking support —> |
| Bluetooth subsystem support —> |
| [*] Bluetooth subsystem support |
| <M> RFCOMM protocol support |
+——————————————————————–+
| <Select> <Exit> <Help> <Save> <Load> |
+——————————————————————–+
TIP: Press ‘/’ to search for any option by name

Installing and Running menuconfig

# Install the required ncurses library
sudo apt install libncurses-dev# Launch menuconfig from inside the kernel source directory
cd linux-6.9/
make menuconfig# Your changes are saved to .config when you choose Save and Exit

make xconfig — Graphical Configuration (Requires Desktop)

Opens a GUI-based configuration tool built with Qt. Easier to explore with a mouse, has a built-in search feature, and shows option descriptions clearly. Only works when you have a desktop environment available. Good for learning and exploration when you are on a local machine.

Installing and Running xconfig

sudo apt install qtbase5-dev
make xconfig

make defconfig — Good Starting Point

Creates a reasonable default .config based on the architecture you are building for. For embedded ARM boards, chipset vendors and board manufacturers often provide their own defconfig file. For example, the Raspberry Pi 4 has bcm2711_defconfig.

Using defconfig

# Default config for your current x86 machine
make defconfig# Default config for Raspberry Pi 4 (ARM64)
# Set ARCH and CROSS_COMPILE first (see Section 6), then:
make bcm2711_defconfig# List all available defconfigs for ARM64
ls arch/arm64/configs/

make oldconfig — For Kernel Version Upgrades

When you upgrade from one kernel version to a newer one, the new kernel may have added new CONFIG options that your existing .config does not have. make oldconfig reads your existing config and only asks you about the new options — keeping everything else unchanged. Always use this when bumping from kernel 6.8 to 6.9, for example.

Using oldconfig for Kernel Upgrades

# Copy your existing config from the running kernel
cp /boot/config-$(uname -r) .config# Update it for the new kernel version
# It will ask you about any NEW options only
make oldconfig

make localmodconfig — Fastest Compile for Your Machine

This is a clever time-saver. It looks at what kernel modules are currently loaded on your running system and disables everything else. The result is a dramatically smaller .config — only the drivers your hardware actually uses. Compile time drops from 60 minutes to often under 15 minutes on the same machine.

Using localmodconfig

# Capture currently loaded modules
lsmod > /tmp/lsmod.txt# Generate a minimal config based on your hardware
make LSMOD=/tmp/lsmod.txt localmodconfig# WARNING: Only use this for building kernels for the SAME machine.
# Do not use it for kernels you intend to run on other hardware.

5. Compiling the Linux Kernel from Source

Once you have your .config ready, compiling is straightforward. The most important flag to know is -j which enables parallel compilation using multiple CPU cores.

Complete Linux Kernel Build and Install Steps

# Step 1: Install build dependencies
sudo apt install gcc make libssl-dev libelf-dev bison flex bc# Step 2: Configure (using one of the methods from Section 4)
make menuconfig# Step 3: Compile using all available CPU cores
# $(nproc) gives you the core count automatically
make -j$(nproc)# This takes 20-60 minutes depending on config size and CPU speed.
# Near the end you will see:
# OBJCOPY arch/x86/boot/bzImage
# Kernel: arch/x86/boot/bzImage is ready

# Step 4: Install loadable kernel modules
sudo make modules_install

# Step 5: Install the kernel image and update /boot
sudo make install

# Step 6: Update bootloader so you can select the new kernel
sudo update-grub

# Step 7: Reboot and select the new kernel from GRUB menu
reboot

What Output Files Does the Build Produce?

Linux Kernel Build Output Files

File | Location | Description
————–|————————|——————————————
vmlinux | Source root / | Uncompressed ELF binary (use for debugging
| | with GDB or the crash tool)
bzImage | arch/x86/boot/ | Compressed bootable kernel for x86.
| | This is copied to /boot/ by make install
Image | arch/arm64/boot/ | Uncompressed kernel for ARM64 boards
zImage | arch/arm/boot/ | Compressed kernel for older ARM boards
*.ko files | Scattered in source | Loadable modules. Installed to
| | /lib/modules/$(uname -r)/ by
| | make modules_install
*.dtb files | arch/arm64/boot/dts/ | Device Tree Blobs for ARM boards

Verifying Your New Kernel After Reboot

Checking the Running Kernel Version

# Show which kernel you are currently running
uname -r
# Example output: 6.9.0# More detailed kernel and system info
uname -a
# Example: Linux myhost 6.9.0 #1 SMP PREEMPT_DYNAMIC … x86_64 GNU/Linux# List all installed kernels on the system
ls /boot/vmlinuz-*

6. Cross-Compilation for Embedded Targets

In embedded Linux development, you typically compile the kernel on a powerful x86 laptop or desktop (the host machine) but the resulting kernel will run on an ARM or RISC-V board (the target machine). This is called cross-compilation.

Cross-Compilation Flow for Embedded Linux

HOST MACHINE TARGET MACHINE
(your x86_64 laptop) (ARM64 embedded board)
+——————-+ +——————-+
| Linux source code | | |
| | cross-compile | kernel Image |
| aarch64-linux- | ———–> | + *.ko modules |
| gnu-gcc compiler | produces | + *.dtb files |
| | | |
+——————-+ +——————-+Why cross-compile?
– The ARM board’s CPU is too slow to compile the kernel itself
– The board has too little RAM and storage for the build tools
– Cross-compilation on a fast host takes minutes instead of hours

Cross-Compiling the Kernel for ARM64 (Raspberry Pi 4 Example)

# Install the ARM64 cross-compiler toolchain
sudo apt install gcc-aarch64-linux-gnu# Set environment variables to tell make which toolchain to use
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-# Configure using the Raspberry Pi 4 defconfig
make bcm2711_defconfig# Optionally customize further
make menuconfig

# Compile (same flags as native build)
make -j$(nproc)

# The bootable kernel image is at:
# arch/arm64/boot/Image

# Device Tree Blobs for the Pi 4 are at:
# arch/arm64/boot/dts/broadcom/bcm2711-rpi-4-b.dtb

What Is a Device Tree (DTB) and Why Does ARM Need It?

On ARM and RISC-V embedded boards, there is no standard way for the kernel to auto-detect what hardware is connected (unlike x86 which uses PCI and ACPI for hardware discovery). Instead, a Device Tree Blob (DTB) is a small binary file that describes the hardware layout — which peripherals exist, at which memory addresses, which interrupt lines they use, and how they are connected.

The bootloader loads both the kernel Image and the DTB and passes the DTB memory address to the kernel at boot time. The kernel reads it and knows exactly what hardware it has to drive.

7. Finding and Modifying CONFIG Options

With thousands of CONFIG options available, here are the practical ways to find the one you need:

Four Methods to Find the Right CONFIG Option

# Method 1: Search inside menuconfig
# Run make menuconfig then press ‘/’ to open the search box
# Type: BLUETOOTH
# It shows all matching options and where they live in the menu# Method 2: grep the .config file after configuration
grep -i bluetooth .config# Method 3: Use the kernel’s scripts/config helper (scriptable)
scripts/config –enable CONFIG_BLUETOOTH
scripts/config –module CONFIG_BT_RFCOMM
scripts/config –disable CONFIG_WIRELESS# Method 4: Find which CONFIG controls a specific source file
# You want to know what controls drivers/bluetooth/hci_uart.c
grep “hci_uart” drivers/bluetooth/Makefile
# Output: obj-$(CONFIG_BT_HCIUART) += hci_uart.o
# So CONFIG_BT_HCIUART is what you need to enable

8. Working with Kernel Modules

Features compiled as m (modules) produce .ko files and are not loaded automatically at boot. You manage them using these commands:

Essential Kernel Module Commands

# List all currently loaded modules
lsmod# Load a module and its dependencies automatically
sudo modprobe bluetooth# Load a specific .ko file manually (no dependency resolution)
sudo insmod /path/to/my_driver.ko# Remove a loaded module
sudo modprobe -r bluetooth
# or equivalently:
sudo rmmod bluetooth

# Get information about a module (author, license, parameters)
modinfo bluetooth

# See what parameters a module accepts
modinfo bluetooth | grep parm

Built-in vs Module — Which Should You Choose?

In embedded Linux, you often face this choice. Building everything as y (built-in) makes boot faster and avoids the overhead of loading modules at runtime — important for real-time systems. Using m (modules) produces a smaller kernel image and lets you load drivers on demand — useful when you have many optional peripherals. Most embedded products that ship a fixed hardware configuration prefer built-in for their core drivers.

9. Common Mistakes and Troubleshooting Tips

⚠ Common Mistake 1 — Skipping the Configuration Step

Even if you do not want to change any settings, you must run at least one configuration step (at minimum make oldconfig) before compiling. Certain auto-generated headers that the build system needs are only produced during configuration. Skipping it causes mysterious compile errors.

⚠ Common Mistake 2 — Missing Build Dependencies

The kernel build needs several packages installed. If you see errors about missing openssl/ssl.h or libelf.h, install the dependencies:

Installing All Kernel Build Dependencies on Ubuntu

sudo apt install gcc make libssl-dev libelf-dev \
bison flex bc dwarves libncurses-dev

⚠ Common Mistake 3 — Forgetting to Run modules_install

After building, if you run make install but forget sudo make modules_install, the new kernel will boot but any feature you compiled as a module will fail to load. Always run modules_install before rebooting into the new kernel.

⚠ Common Mistake 4 — Wrong ARCH or CROSS_COMPILE for Embedded

If you forget to set ARCH and CROSS_COMPILE before configuring or compiling for an ARM board, the build system will default to x86 and produce a kernel your ARM board cannot run. Always set these before any make command when cross-compiling.

10. Complete Workflow — Quick Reference for This Free Linux Kernel Development Course

From Source to Running Kernel — Full Step-by-Step

STEP 1: Get the source
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.9.tar.xz
tar -xf linux-6.9.tar.xz
cd linux-6.9STEP 2: Install dependencies
sudo apt install gcc make libssl-dev libelf-dev bison flex bc dwarvesSTEP 3: Configure
make menuconfig <– for x86 (your laptop/desktop)
OR
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
make bcm2711_defconfig <– for Raspberry Pi 4STEP 4: Compile
make -j$(nproc) <– uses all CPU cores

STEP 5: Install modules
sudo make modules_install

STEP 6: Install kernel (x86 only)
sudo make install
sudo update-grub

STEP 7: Transfer to board (embedded ARM)
Copy arch/arm64/boot/Image and *.dtb to your SD card or TFTP server

STEP 8: Reboot and verify
uname -r <– should show your new version

📌 Key Takeaway from This Free Linux Kernel Development Lecture

Kernel configuration is not a one-time thing — it is something you revisit every time you change hardware, upgrade the kernel version, or need to enable a new driver. The more you practice with menuconfig, the faster you get at knowing exactly where an option lives. Start with make localmodconfig to get a small, fast-building config, and gradually customize from there.

🎓 Interview Questions and Answers

Q1. What is the kbuild system and what problem does it solve?

kbuild is the Linux kernel’s build infrastructure, built on top of GNU make. It solves the problem of managing a massively complex codebase where what gets compiled depends entirely on user-chosen configuration options. It ties together Kconfig (option definitions), the .config file (user choices), and per-directory Makefiles (what to compile) into a single coordinated build system.

Q2. What is the difference between CONFIG_FOO=y and CONFIG_FOO=m?

CONFIG_FOO=y means the feature is compiled into the kernel image — always present from the moment the kernel boots, no additional loading required. CONFIG_FOO=m means it is compiled as a separate loadable kernel module (.ko file) that can be loaded later with modprobe. Modules allow flexibility but add a small latency at load time. Boot-critical features must always be y.

Q3. What is the purpose of make localmodconfig?

It generates a minimal .config based only on the kernel modules currently loaded on your running system (from lsmod output). Everything else is disabled. This dramatically reduces compile time because you are not compiling hundreds of drivers your hardware does not use. It is extremely useful during development but should not be used for building kernels intended to run on other machines with different hardware.

Q4. What are Kconfig files and how do they differ from Makefiles?

Kconfig files define what configuration options exist — their descriptions, allowed states (y/m/n), and dependencies on other options. Makefiles define what source files to compile based on those options. A typical Makefile pattern: obj-$(CONFIG_BLUETOOTH) += bluetooth.o — compile bluetooth.o only if CONFIG_BLUETOOTH is set. The two work together: Kconfig defines the menu, Makefile acts on the result.

Q5. What is cross-compilation and why is it needed for embedded Linux development?

Cross-compilation is building software on one machine type (host, typically x86) to run on a different machine type (target, typically ARM or RISC-V). It is needed in embedded development because the target device has a slow CPU and limited storage and RAM. You use your powerful development machine with a cross-compiler toolchain (like aarch64-linux-gnu-gcc) to produce binaries for the target architecture quickly.

Q6. What is a Device Tree and why does ARM Linux need it while x86 does not?

A Device Tree is a data structure (compiled into a .dtb binary file) that describes the hardware layout of a board to the kernel — what peripherals are connected, at which memory addresses, which interrupt lines they use. ARM and RISC-V embedded boards require it because they lack standardized hardware discovery mechanisms like PCI or ACPI that x86 machines use. Without a Device Tree the kernel would not know what hardware it has to initialize and drive.

Q7. When should you use make oldconfig versus make menuconfig?

Use make oldconfig when you are upgrading an existing kernel to a newer version and want to keep your existing configuration as-is, only responding to new options added in the newer kernel. Use make menuconfig when you want to interactively explore and change configuration options — starting fresh, doing a significant reconfiguration, or enabling a specific new driver or feature.

Q8. What command tells you the number of CPU cores available for parallel compilation?

The command nproc prints the number of available processing units. It is used with the -j flag of make: make -j$(nproc). This allows make to compile multiple source files simultaneously, dramatically reducing build time. On a quad-core machine this alone cuts compile time to roughly one quarter of the serial build time.

← Previous Lecture
Chapter 2 — Part 2 of 2
Next Lecture →

EmbeddedPathashala — Free Embedded Systems Education — embeddedpathashala.com

Leave a Reply

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