Part 3:Three Practical Approaches to Kernel Configuration-Linux Device Drivers Course

📚 Linux Kernel Programming Series  |  ← Part 1: Kbuild  |  ← Part 2: Kernel Config  |  Part 3: Config Approaches
Linux Kernel Programming | Part 3

Three Approaches to Kernel Configuration

Embedded system, distro copy, or lean custom build? Here is how to pick the right starting config for every situation.

⏱ ~30 min read
🎯 Intermediate
🐧 Kernel 6.x Updated

What you will learn in this part

  • Approach 1: Using a board-specific defconfig for embedded Linux systems
  • Approach 2: Starting from your distribution’s existing kernel config
  • Approach 3: The localmodconfig approach — a lean, minimal config
  • When to use which approach and the trade-offs of each
  • How to verify your final config is consistent and correct
  • Practical step-by-step commands for each approach

Why Your Starting Point Matters So Much

Picking the wrong starting config is one of the most common mistakes when building the Linux kernel for the first time. Here is why it matters so much in practice:

❌ Too large a config

A typical Ubuntu or Debian kernel config enables thousands of drivers and features — including drivers for hardware that does not exist on your target machine. The kernel takes much longer to compile, the image is larger, boot time increases, and on embedded systems you may simply run out of flash storage.

❌ Missing critical drivers

If you start from a too-minimal config (like make defconfig) without understanding your hardware, you may miss the storage controller driver — and the kernel will panic at boot because it cannot find the root filesystem. Or you miss the network driver and the system is unreachable after deployment.

The three approaches below solve different versions of this problem depending on your target and goals. Let us go through each one carefully.

Approach 1

Board-Specific defconfig for Embedded Systems

Best for: Raspberry Pi, BeagleBone, custom SoC boards, any non-x86 target

If you are building Linux for a specific embedded board — a Raspberry Pi, a Rockchip SoC, a Qualcomm mobile platform, an NXP i.MX board — the Linux kernel source tree itself gives you a head start. The kernel developers and SoC vendors contribute ready-made, tested configuration files for a huge number of hardware platforms.

These files live in the kernel source under arch/<architecture>/configs/ and are named in the format <board-name>_defconfig.

Linux 6.x — arch/ directory structure (selected)
arch/
├── arm/
│   └── configs/
│       ├── bcm2835_defconfig      ← Raspberry Pi Zero / Pi 1 (BCM2835 SoC)
│       ├── imx_v6_v7_defconfig    ← NXP i.MX6/i.MX7 boards
│       ├── omap2plus_defconfig    ← TI OMAP / BeagleBone
│       ├── multi_v7_defconfig     ← Generic ARM v7 multi-platform
│       └── ...                    ← 100+ board configs
├── arm64/
│   └── configs/
│       ├── defconfig              ← Generic ARM64 (covers RPi 3/4/5)
│       ├── bcm2835_defconfig
│       └── ...
├── riscv/
│   └── configs/
│       ├── defconfig
│       └── rv32_defconfig
└── x86/
    └── configs/
        ├── i386_defconfig
        └── x86_64_defconfig

Step-by-Step: Using a Board defconfig

# Step 1: Find available defconfigs for your architecture
ls arch/arm64/configs/      # For 64-bit ARM (RPi 3, 4, 5)
ls arch/arm/configs/        # For 32-bit ARM boards

# Step 2: Load the board's defconfig as your starting .config
# This creates a .config from that board's defconfig file
make ARCH=arm64 defconfig               # Generic ARM64
make ARCH=arm64 bcm2711_defconfig       # Raspberry Pi 4 (BCM2711)
make ARCH=arm omap2plus_defconfig       # BeagleBone Black

# Step 3: Optionally fine-tune with menuconfig
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- menuconfig

# Step 4: Build
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc) Image modules dtbs

# Step 5: Install modules (on the target or in a sysroot)
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- INSTALL_MOD_PATH=/path/to/rootfs modules_install

✅ Why this approach works well

  • The defconfig was created and tested by someone who knows exactly what that board needs
  • It includes only what is relevant for that hardware — no x86 PC Card drivers or irrelevant file systems
  • Critical things like the correct SoC clock driver, MMC storage driver, and UART console are already enabled
  • You can still add or remove things on top of this via menuconfig
Kernel 6.x update: The Raspberry Pi 4 (BCM2711) and Pi 5 (BCM2712) defconfigs are well-maintained in the mainline kernel as of 6.1+. For the very latest Pi 5 support, Linux 6.6+ is recommended. The RPi Foundation also maintains their own kernel fork with additional patches, but mainline support has significantly improved.
Approach 2

Start from Your Distribution’s Config

Best for: Custom kernel on your own desktop/laptop/server

If you are building a custom kernel for the same x86 machine you are sitting at — maybe you want a newer version, or need to add/remove a specific driver, or want to experiment with real-time patches — the safest starting point is your distribution’s own kernel config.

Your distro kernel has already been built and tested on your hardware. It knows which storage controller driver you need, which GPU driver, which filesystem. Starting from it means you inherit all of that knowledge.

Where to Find Your Distro’s Config

# Method 1: The /boot directory (most common on Ubuntu, Debian, Fedora)
ls /boot/config-$(uname -r)
# Example output: /boot/config-6.8.0-49-generic

# Method 2: /proc/config.gz — if running kernel has CONFIG_IKCONFIG_PROC=y
# (Common on Arch Linux, some Fedora builds)
zcat /proc/config.gz | head -10

# Method 3: /lib/modules directory
ls /lib/modules/$(uname -r)/.config 2>/dev/null

Step-by-Step: Using Distro Config

# Step 1: Download the kernel source (always from kernel.org)
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.12.tar.xz
tar xf linux-6.12.tar.xz
cd linux-6.12

# Step 2: Copy your running kernel's config as the starting point
cp /boot/config-$(uname -r) .config

# Step 3: Update the config for the new kernel version
# This handles new options added since your distro config was created
# It sets all new options to their defaults — no manual prompting
make olddefconfig

# Step 4: Open menuconfig to review or customize
# (optional — skip this if you just want the same config as your distro)
make menuconfig

# Step 5: Build the kernel
make -j$(nproc)

# Step 6: Install modules and then the kernel
sudo make modules_install
sudo make install        # Copies kernel and updates bootloader on most distros
⚠️ Common compile error when using distro configs:

Ubuntu and Debian kernel configs often have CONFIG_SYSTEM_TRUSTED_KEYS set to a path pointing to Canonical’s/Debian’s kernel signing keys — which do not exist on your machine. You will get a build error. Fix it:

scripts/config --set-str CONFIG_SYSTEM_TRUSTED_KEYS ""
scripts/config --set-str CONFIG_SYSTEM_REVOCATION_KEYS ""

✅ Advantages

  • Guaranteed to support your hardware
  • All critical drivers are already included
  • Minimal risk of an unbootable kernel
  • Great for learning — just change one thing at a time

⚠️ Disadvantages

  • Very large config — 10,000+ options set
  • Compile time is very long (1–3 hours on average hardware)
  • Contains drivers for hardware you will never use
  • Distro-specific patches may not apply cleanly to vanilla kernel

📊 How big is a typical Ubuntu kernel config?

# Count total config options
grep -c "^CONFIG_" /boot/config-$(uname -r)
# Typical output: ~10,000 to 12,000 options

# Count enabled options (y or m)
grep -c "=y\|=m" /boot/config-$(uname -r)
# Typical output: ~6,000 to 8,000 options enabled

# Count loadable modules enabled
grep -c "=m$" /boot/config-$(uname -r)
# Typical output: ~4,000+ modules

This is why a full distro config kernel takes so long to compile — there are thousands of modules being built.

Approach 3

The localmodconfig Approach

Best for: Fast builds, minimal kernels, driver development, learning

The localmodconfig approach is clever. Instead of asking you which drivers you want, it looks at your currently running system and asks: “Which kernel modules are loaded right now?” Then it creates a minimal kernel config that includes exactly those modules and turns off everything else.

The result is a very lean kernel that still fully supports your specific hardware — because if a module is currently loaded, it obviously means your hardware needs it. No more, no less.

How localmodconfig Works Internally
1

Runs lsmod to get the list of all currently loaded kernel modules

2

For each loaded module, it looks up the corresponding CONFIG symbol in the kernel source

3

Generates a minimal .config with those modules enabled and everything else turned off

4

You can then add any additional options you need via menuconfig on top of this lean base

Step-by-Step: Using localmodconfig

# Step 1: Make sure all your hardware is active before running this
# Plug in USB devices, connect WiFi, mount drives, etc.
# localmodconfig only knows about CURRENTLY LOADED modules

# See what is currently loaded
lsmod | head -20
lsmod | wc -l      # Typical: 80-150 modules on a desktop

# Step 2: Go into the kernel source tree
cd linux-6.12

# Step 3: Run localmodconfig
# If you have a previous .config to start from:
cp /boot/config-$(uname -r) .config
make localmodconfig

# Or start completely fresh (uses a generic defconfig as base):
make localmodconfig    # Will ask about options it is unsure about

# Step 4: Review or customize
make menuconfig        # Optional — add anything localmodconfig missed

# Step 5: Build
make -j$(nproc)

# Compare module count before and after:
# Before (distro config): ~4000 modules
# After (localmodconfig): ~100-200 modules — much faster build!
⚠️ The #1 trap with localmodconfig:

localmodconfig only captures modules that are currently loaded at the time you run it. If you have a USB WiFi dongle but it is not plugged in right now, its driver will not be included. If you have a Bluetooth headset but it is off, its driver may be missing. If you want to boot from a different filesystem later, it might not be compiled in.

Solution: Before running make localmodconfig, plug in all your hardware, connect WiFi, mount all drives, and use everything you plan to use. Then run the command. Alternatively, use make menuconfig after localmodconfig to manually add anything you know you will need.

Build Time Comparison (Approximate — 8-core machine)
Starting Config Modules Built Compile Time
Distro config (Ubuntu) 4,000+ 60–120 min
Distro config + localmodconfig 100–200 8–15 min
Board defconfig (ARM embedded) 50–150 5–12 min

Times vary significantly based on hardware, SSD vs HDD, and exact config

Choosing the Right Approach — Decision Guide

Your Situation Best Approach Why
Building for Raspberry Pi / BeagleBone / embedded SoC Approach 1 Board defconfig already knows what that hardware needs
Custom kernel for your own laptop / desktop — reliability matters Approach 2 Distro config guarantees your hardware works — just add your customization
Fast iteration — want to recompile often while developing a driver Approach 3 localmodconfig gives a lean build — rebuild in minutes not hours
Learning — just want to experiment and see what options exist Approach 3 Quick builds mean faster feedback loop — build, test, change, repeat
Production system where stability is non-negotiable Approach 2 Start from a proven config, change minimum, test exhaustively

Verifying Your Configuration is Consistent

Regardless of which approach you choose, after finalizing your .config you should always run these verification steps:

# 1. Resolve any dependency inconsistencies — ALWAYS run this after any config change
make olddefconfig
# This updates .config to satisfy all Kconfig dependencies
# Sets any missing options to their default values

# 2. Run a quick sanity check
make prepare
# This runs some pre-build checks without actually compiling

# 3. Check for new options you might have missed
make listnewconfig
# Lists all CONFIG options present in Kconfig but missing from your .config

# 4. Look at what you have enabled (useful after localmodconfig)
grep "=y" .config | wc -l     # Count built-in features
grep "=m" .config | wc -l     # Count modules
grep "not set" .config | wc -l  # Count disabled features

# 5. Verify the config file version matches the kernel source
head -3 .config
# Should show: # Linux/x86_64 6.12.0 Kernel Configuration

Complete Series Recap

Part 1 — Kbuild
  • CONFIG_FOO symbols
  • y / m / n values
  • Kconfig files
  • Recursive Makefiles
  • The .config file
Part 2 — Config Tools
  • DEFCONFIG_LIST priority
  • menuconfig / nconfig
  • oldconfig / olddefconfig
  • scripts/config utility
  • Production vs learning
Part 3 — Approaches
  • Board defconfig (embedded)
  • Distro config (safe start)
  • localmodconfig (lean)
  • Decision guide
  • Config verification

Interview Questions

Q1. What are board-specific defconfig files and where do they live in the kernel source?
Board-specific defconfig files are pre-made, tested kernel configuration files contributed by SoC vendors and board maintainers. They live in arch/<architecture>/configs/ inside the kernel source tree and follow the naming pattern <boardname>_defconfig. For example, arch/arm64/configs/bcm2711_defconfig is for the Raspberry Pi 4 (which uses Broadcom’s BCM2711 SoC). To use one, you run make ARCH=arm64 bcm2711_defconfig, which copies that defconfig as your working .config. These configs include exactly what that specific hardware needs and nothing more.
Q2. What does make localmodconfig do and what is its biggest limitation?
make localmodconfig scans the output of lsmod (currently loaded kernel modules) and generates a minimal .config that includes only those modules and their dependencies, turning off everything else. The resulting kernel is much smaller and compiles significantly faster. Its biggest limitation is that it only knows about hardware that is currently active — if a device is not plugged in or not loaded at the time you run the command, the driver for it will not be included. For example, if your USB WiFi adapter is unplugged, WiFi will not work on the new kernel. The solution is to plug in all hardware before running localmodconfig.
Q3. When building a custom kernel using your distro’s config, what common compile error might you get and how do you fix it?
The most common error when using Ubuntu or Debian kernel configs is related to CONFIG_SYSTEM_TRUSTED_KEYS and CONFIG_SYSTEM_REVOCATION_KEYS. These options point to paths where the distribution’s kernel signing certificates are stored. Since those certificate files do not exist on your build machine, the build fails with an error about missing certificate files. The fix is to clear those values using scripts/config --set-str CONFIG_SYSTEM_TRUSTED_KEYS "" and scripts/config --set-str CONFIG_SYSTEM_REVOCATION_KEYS "", then run make olddefconfig to resolve dependencies.
Q4. Why should you always run make olddefconfig after manually editing config settings?
The Kconfig dependency system creates constraints between options — enabling one option may require another to be enabled, or may conflict with a third. When you change settings using scripts/config or manually, these dependencies are not automatically resolved. make olddefconfig reads the Kconfig files, checks your current .config against all the dependencies, and resolves any inconsistencies by setting conflicting or missing options to their default values without prompting. If you skip this step, the build may fail with confusing errors about undefined symbols or missing configs.
Q5. How would you approach kernel configuration differently for an embedded product vs a kernel development workstation?
For an embedded product: start from the board’s official defconfig, work closely with the hardware bring-up team to identify exactly which drivers are needed, use make menuconfig to strip anything not required, keep the image as small as possible (flash storage is expensive), test extensively on real hardware, and lock the config in version control. For a kernel development workstation: use your distro’s config as the base (reliability over speed), then apply localmodconfig to speed up rebuilds during development, enable debug options like CONFIG_KASAN, CONFIG_DYNAMIC_DEBUG, and CONFIG_KALLSYMS, and keep a known-good kernel installed as a fallback. The priorities are completely different — one optimizes for stability and size, the other for debug visibility and rebuild speed.
Q6. How can you check your running kernel’s configuration without having access to /boot/config-*?
If the kernel was built with CONFIG_IKCONFIG=y (kernel config support) and CONFIG_IKCONFIG_PROC=y (expose via /proc), the running kernel stores a compressed copy of its own configuration inside itself. You can read it at runtime using zcat /proc/config.gz. This is particularly useful on embedded systems where you may not have access to the /boot directory, or when analyzing a kernel binary on a different machine. Arch Linux kernels typically enable this. To check if it is available: ls /proc/config.gz. If the file does not exist, CONFIG_IKCONFIG_PROC was not set at build time.
← Part 2: Kernel Configuration
Linux Kernel Programming | EmbeddedPathashala
Series Complete ✓

2 Comments

Leave a Reply

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