Skip to content

Week 2 — The Kernel Build System: Kconfig and menuconfig

Goal

Understand how the kernel's configuration system works, how to navigate menuconfig, and how to create a minimal config suitable for QEMU. By the end of this week, you'll be able to find any option, understand its dependencies, and make informed choices about what goes into your kernel.

Why This Matters

The kernel has over 15,000 configurable options. The configuration you choose determines what hardware is supported, what features are available, how big the kernel is, and how fast it builds. For kernel development, you want a minimal config that boots in QEMU quickly — not the kitchen-sink config your distro ships.


Kconfig: The Configuration Language

Every directory in the kernel has a Kconfig file that defines the options for that subsystem. These files use a simple declarative language.

Look at a real example:

# Open the networking Kconfig
nvim net/Kconfig

You'll see entries like:

menuconfig NET
    bool "Networking support"
    ---help---
      Unless you really know what you are doing, you should say Y here.

config INET
    bool "TCP/IP networking"
    depends on NET

Key concepts:

  • bool — option is either Y (yes) or N (no)
  • tristate — option can be Y (built-in), M (module), or N (disabled)
  • depends on — this option only appears if its dependency is enabled
  • select — enabling this option forces another option on (use with care)
  • default — the default value if the user doesn't choose
  • menuconfig — creates a submenu that can be entirely disabled

The tristate distinction matters:

  • =y — compiled directly into vmlinux. Always present. Can't be unloaded.
  • =m — compiled as a .ko module. Loaded on demand. Can be unloaded.
  • =n — not compiled at all.

For development, modules (=m) are convenient because you can rebuild and reload a single module without rebooting.

make menuconfig

This opens an ncurses-based UI. Navigation:

  • Arrow keys — move between options
  • Enter — enter a submenu
  • Space — cycle through Y/M/N
  • ? — show help for the highlighted option (including dependencies)
  • / — search for an option by name
  • Esc Esc — go back / exit

The search (/) is your most important tool. Type /TCP and you'll see every option mentioning TCP, along with its location in the menu hierarchy, its current value, and its dependencies.

Other Configuration Targets

make defconfig      # Default config for your architecture (minimal, boots on QEMU)
make allnoconfig    # Everything disabled — absolute minimum
make allyesconfig   # Everything enabled — huge, slow, used for build testing
make allmodconfig   # Everything as module where possible
make localmodconfig # Disables modules not currently loaded on your system
make tinyconfig     # Smallest possible kernel
make xconfig        # Qt-based GUI (needs qt5 dev packages)
make nconfig        # Alternative ncurses UI

Creating a Minimal QEMU Config

The distro config you copied in Week 1 builds thousands of drivers you don't need. Let's make a lean config for development:

make defconfig

This gives you a config that boots on x86_64 QEMU with virtio drivers. It's a great starting point. Now enable what we need for networking development:

make menuconfig

Navigate and enable these (use / to search):

  1. Networking support → should already be =y
  2. Networking options → enable what you want to test (TCP, UDP, IPv6, etc.)
  3. Device Drivers → Network device support → Virtual drivers → virtio network=y or =m
  4. Device Drivers → Virtio drivers → ensure virtio core is enabled
  5. Kernel hacking → Kernel debugging=y
  6. Kernel hacking → Compile-time checks → Debug info (DWARF) → enable for GDB
  7. General setup → Configure standard kernel features → keep defaults

Save and exit.

Understanding .config

After saving, examine .config:

# See what's set
grep -c '=y' .config    # Built-in options
grep -c '=m' .config    # Module options
grep '=n' .config        # Won't appear! Disabled options are commented out

# Disabled options look like this:
grep 'CONFIG_BLUETOOTH' .config
# # CONFIG_BLUETOOTH is not set

Important: Never edit .config by hand. If you need to change options programmatically, use:

scripts/config --enable CONFIG_SOMETHING
scripts/config --disable CONFIG_SOMETHING
scripts/config --module CONFIG_SOMETHING

Then run make olddefconfig to resolve dependencies.

How Configuration Flows Into the Build

When you run make, the build system:

  1. Reads .config
  2. Generates include/generated/autoconf.h — C #define statements
  3. Generates include/config/auto.conf — Makefile variables

In C code, you'll see:

#ifdef CONFIG_IPV6
    /* IPv6-specific code */
#endif

In Makefiles, you'll see:

obj-$(CONFIG_IPV6) += ipv6.o

If CONFIG_IPV6=y, this becomes obj-y += ipv6.o (built-in). If CONFIG_IPV6=m, this becomes obj-m += ipv6.o (module). If not set, the file is never compiled.

Build Targets You Should Know

make -j$(nproc)              # Build everything (vmlinux + bzImage + modules)
make -j$(nproc) bzImage      # Build only the kernel image
make -j$(nproc) modules      # Build only modules
make net/ipv4/tcp.o           # Build a single object file (great for quick compile checks)
make net/ipv4/                # Build everything in a directory
make M=net/ipv4               # Build out-of-tree style for a directory
make C=1                      # Run sparse (static analyzer) during build
make W=1                      # Extra compiler warnings

The single-file build (make net/ipv4/tcp.o) is invaluable during development. You can check if your changes compile without building the entire kernel.

Exercises

  1. Run make defconfig, then make menuconfig. Use / to find CONFIG_BPF. Read its help text and note its dependencies.
  2. Compare the line counts: wc -l .config after defconfig vs the distro config from Week 1. The difference is why defconfig builds are faster.
  3. Look at net/ipv4/Makefile. Find the line for tcp.o. What config option controls whether it's built? (Trick question — TCP is always built if INET is enabled.)
  4. Run scripts/config --enable CONFIG_NET_SCH_FQ then make olddefconfig. Check if any new options appeared in .config due to dependencies.

What's Next

Next week we look at what happens between pressing the power button and reaching a shell — the Linux boot process. Understanding this is essential because when your kernel doesn't boot, you need to know where it failed.