Skip to content

Kbuild: The Kernel Build System

Makefiles, Kconfig, out-of-tree modules, and cross-compilation

Overview

The kernel build system (Kbuild) is built on top of GNU Make. It uses a hierarchy of Makefile fragments across the source tree and a powerful configuration language (Kconfig):

make menuconfig    → generates .config (symbol definitions)
make               → compiles kernel using .config
make modules_install → installs modules to /lib/modules/$(uname -r)/

.config: kernel configuration

# Start fresh from a default config:
make defconfig          # minimal config for current arch
make allmodconfig       # enable everything as modules
make allyesconfig       # enable everything built-in
make localmodconfig     # minimal config matching currently loaded modules

# Interactive config tools:
make menuconfig         # text-based menu (requires ncurses-dev)
make xconfig            # Qt GUI
make gconfig            # GTK GUI
make nconfig            # newer ncurses UI

# Use existing config as base:
cp /boot/config-$(uname -r) .config
make olddefconfig        # accept defaults for new symbols

# Minimal config for compiling a specific driver:
make randconfig         # random config (for testing)

Config file format

# .config format: each CONFIG_SYMBOL has one of:
CONFIG_SMP=y          # built-in (y = yes)
CONFIG_MODULES=y      # required for loadable modules
CONFIG_EXT4_FS=m      # built as module
# CONFIG_NFS_FS is not set   # disabled

# Query a symbol:
./scripts/config --state CONFIG_SMP
# y

# Set a symbol:
./scripts/config --enable CONFIG_DEBUG_INFO
./scripts/config --disable CONFIG_SWAP
./scripts/config --module CONFIG_EXT4_FS
./scripts/config --set-val CONFIG_LOG_BUF_SHIFT 18

Kconfig language

# drivers/net/ethernet/intel/Kconfig

config E1000
    tristate "Intel(R) PRO/1000 Gigabit Ethernet support"
    depends on PCI
    help
      This driver supports Intel(R) PRO/1000 gigabit ethernet family of
      adapters.  For more information on how to identify your adapter, go
      to the Adapter & Driver ID Guide at:
      <http://support.intel.com/support/go/network/adapter/home.htm>

config E1000E
    tristate "Intel(R) PRO/1000 PCI-Express Gigabit Ethernet support"
    depends on PCI && (!SPARC32)
    select PTP_1588_CLOCK
    default y if X86
    help
      ...

# Types:
# bool:     y or n
# tristate: y, m (module), or n
# string:   CONFIG_DEFAULT_HOSTNAME="(none)"
# int:      CONFIG_LOG_BUF_SHIFT=18
# hex:      CONFIG_PAGE_OFFSET=0xC0000000

# Dependencies:
# depends on A && B    → both A and B must be y/m
# depends on A || B    → at least one
# depends on !A        → A must not be set
# select A             → force-enable A (ignores depends)
# imply A              → suggest enabling A (can be overridden)

Kbuild Makefiles

Each directory has a Makefile that tells Kbuild what to compile:

# drivers/net/ethernet/intel/e1000/Makefile

# obj-$(CONFIG_E1000): 'y' → built-in, 'm' → module, unset → skip
obj-$(CONFIG_E1000) += e1000.o

# e1000.o is built from multiple source files:
e1000-objs := e1000_main.o e1000_hw.o e1000_ethtool.o e1000_param.o

# Subdirectories:
obj-y += subdirname/
# → recurse into subdirname/Makefile

Top-level build flow

make
├── scripts/Makefile.build: recurse into each subdirectory
├── Each Makefile: contributes obj-y, obj-m to the build
├── vmlinux: link all obj-y into the kernel binary
└── modules: compile all obj-m into .ko files

Building an out-of-tree module

# Makefile for external module (hello_module.c):
obj-m := hello_module.o

KDIR ?= /lib/modules/$(shell uname -r)/build
PWD  := $(shell pwd)

all:
    $(MAKE) -C $(KDIR) M=$(PWD) modules

clean:
    $(MAKE) -C $(KDIR) M=$(PWD) clean

install:
    $(MAKE) -C $(KDIR) M=$(PWD) modules_install
# Build the module:
make

# Install (copies to /lib/modules/$(uname -r)/extra/):
sudo make install
sudo depmod -a       # update module dependency database

# Load:
modprobe hello_module

Multiple source files

obj-m := mydriver.o
mydriver-objs := main.o helper.o init.o
# Builds main.o, helper.o, init.o and links into mydriver.ko

Cross-compilation

# Cross-compile for ARM64 on x86-64 host:
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-

make defconfig      # uses arch/arm64/configs/defconfig
make -j$(nproc)

# The CROSS_COMPILE prefix is prepended to:
# ${CROSS_COMPILE}gcc, ${CROSS_COMPILE}ld, etc.

# For Raspberry Pi (armv7):
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
make bcm2711_defconfig
make -j$(nproc)

# Out-of-tree module cross-compilation:
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- \
     KDIR=/path/to/arm64-kernel-build M=$(PWD) modules

Compiler options and build flags

# Add compilation flags:
ccflags-y += -DDEBUG_MODE
ccflags-y += -I$(src)/include   # $(src) = directory of this Makefile

# Per-file flags:
CFLAGS_hello_module.o += -DTEST_BUILD

# Disable warnings:
CFLAGS_my_file.o += -w
# Build with debugging info (for KGDB, crash analysis):
make CONFIG_DEBUG_INFO=y

# Build with address sanitizer:
make CONFIG_KASAN=y

# Enable compile-time warnings:
make W=1        # extra warnings
make W=2        # more extra warnings
make W=3        # all warnings
make C=1        # sparse static analysis
make C=2        # sparse for all files

# Check build dependencies:
make htmldocs   # build kernel documentation

depmod and module dependencies

# modules.dep: dependency database for modprobe:
depmod -a     # generate for current kernel
depmod -a 5.15.0  # for specific kernel version

# View module dependencies:
modinfo -F depends virtio_net
# virtio,net_failover

# modprobe automatically loads dependencies:
modprobe virtio_net
# Loads: virtio, net_failover, then virtio_net

# Manual depmod output:
cat /lib/modules/$(uname -r)/modules.dep | grep virtio_net
# kernel/drivers/net/virtio_net.ko: kernel/drivers/virtio/virtio.ko ...

Build artifacts

# After make:
# Top-level build directory:
# vmlinux       ← uncompressed ELF kernel (for debugging)
# System.map    ← symbol table (addresses)
ls arch/x86/boot/
# bzImage       ← bootable compressed kernel

# Module signature (if CONFIG_MODULE_SIG_ALL=y):
modinfo drivers/net/ethernet/intel/e1000/e1000.ko | grep sig
# sig_id: PKCS#7
# sig_hashalgo: sha256

# Symbols a module exports:
nm --defined-only drivers/net/ethernet/intel/e1000/e1000.ko | grep " T "
# Only text (function) symbols defined in the module

# Check what a module provides and needs:
modinfo drivers/net/ethernet/intel/e1000/e1000.ko
# filename: ...
# license: GPL v2
# description: Intel(R) PRO/1000 Network Driver
# author: ...
# depends: ptp
# vermagic: 6.1.0-17-amd64 SMP preempt mod_unload modversions

# vermagic: must match running kernel exactly

Useful build targets

make -j$(nproc)             # parallel build
make bzImage                # kernel image only
make modules                # modules only
make modules_install         # install modules to /lib/modules/
make install                 # install kernel image + System.map

# Single file:
make drivers/net/ethernet/intel/e1000/e1000.o

# Single module:
make drivers/net/ethernet/intel/e1000/e1000.ko

# Header dependency check:
make headers_check

# Clean:
make clean     # remove build artifacts (keep .config)
make mrproper  # remove everything including .config
make distclean # mrproper + patches

Further reading

  • Writing and Loading Modules — module init/exit
  • Module Signing — signing out-of-tree modules
  • KGDB — debugging requires debug symbols
  • Documentation/kbuild/ — Kbuild documentation
  • scripts/kconfig/ — Kconfig implementation