Beginner → Intermediate
Linux 6.x
3 of 3
You have written your first kernel module. You have set up your logging with pr_fmt(). Now — how do you actually compile it into a .ko file that the kernel can load?
The answer is the Kbuild system. It is the build infrastructure that compiles not just your module, but the entire Linux kernel. Understanding how it works — and how to write a correct Makefile for your module — is an essential skill for every kernel developer and Linux device driver author.
1. What Is the Kbuild System?
The Linux kernel does not use a plain handwritten Makefile the way a simple C project might. It uses a layered, recursive build system called Kbuild. Kbuild is responsible for compiling thousands of C files with the exact same compiler flags, enforcing configuration choices made through menuconfig, and producing the final kernel image and any kernel modules.
When you build a kernel module outside the kernel source tree — called an out-of-tree module — you are not working in isolation. Your module’s Makefile delegates the actual compilation work to the Kbuild system. This guarantees that your module is compiled with precisely the same compiler settings, ABI conventions, and header definitions as the running kernel it will be loaded into.
|
Role 1: Built-in compilation (obj-y) Compiles kernel subsystems and drivers that are permanently baked into the kernel image (vmlinux). Selected during make menuconfig with Y.
|
| |
Role 2: Module compilation (obj-m) Compiles code as separate .ko files that can be loaded and unloaded at runtime with insmod and rmmod. Selected with M.
|
2. obj-y and obj-m — The Core Concept
Deep inside the Kbuild system, two special Make variables control what gets built and how:
obj-y — Built into the kernel
Anything added to obj-y gets compiled and linked directly into the kernel image — the vmlinux binary. It is always present whenever that kernel boots. The y stands for Yes, matching the Y you select in menuconfig.
# Example from a driver's internal Kbuild file (not your module's Makefile)
# This compiles i2c-core into the kernel image itself
obj-y += i2c-core.o
obj-m — Built as a loadable module
Anything added to obj-m gets compiled into a separate .ko file. It is not present in memory until someone explicitly loads it with insmod or modprobe. The m stands for Module.
This is the variable you use in your own module’s Makefile:
# Your module's Makefile — tell Kbuild to build your source as a module
obj-m += my_gpio_driver.o
When Kbuild sees this, it looks for a file named my_gpio_driver.c, compiles it into my_gpio_driver.o, and then links it into my_gpio_driver.ko.
| Variable | menuconfig choice | Output file | How to load | Can unload? |
|---|---|---|---|---|
| obj-y | Y (built-in) | vmlinux / bzImage | Always in memory at boot | No |
| obj-m | M (module) | driver_name.ko | insmod / modprobe | Yes (rmmod) |
| (not set) | N (disabled) | Not compiled | N/A | N/A |
3. Anatomy of a Complete Kernel Module Makefile
Here is a complete, production-ready Makefile for an out-of-tree kernel module, written for Linux 6.x:
# Kernel module Makefile for Linux 6.x
# Place this file in the same directory as your .c source file
# Module name — must match your .c filename
MODULE_NAME := my_gpio_driver
# Add module to the Kbuild out-of-tree build list
obj-m += $(MODULE_NAME).o
# Locate the kernel headers for the currently running kernel
KERNEL_DIR := /lib/modules/$(shell uname -r)/build
# Save the current working directory
PWD := $(shell pwd)
# === BUILD TARGETS ===
# Default target — builds the .ko file
all:
$(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules
# Install the .ko into the system's module directory
install:
$(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules_install
# Remove all generated build artifacts
clean:
$(MAKE) -C $(KERNEL_DIR) M=$(PWD) clean
# === OPTIONAL — enable pr_debug() output ===
# Uncomment the line below to activate debug messages
# EXTRA_CFLAGS += -DDEBUG
all:) must be indented with a real Tab character, not spaces. If you use spaces, make will throw a missing separator error and refuse to run. Most text editors can be configured to insert tabs; check your editor’s settings if you see this error.
4. How the Build Process Works Step by Step
When you type make in your module directory, here is exactly what happens:
Step 1 — make reads your Makefile and finds the all: target |
| ↓ |
Step 2 — -C $(KERNEL_DIR) tells make to change directory into the kernel headers tree at /lib/modules/$(uname -r)/build |
| ↓ |
| Step 3 — Kbuild parses the kernel’s top-level Makefile. This loads all compiler flags, ABI settings, and architecture configuration for the running kernel |
| ↓ |
Step 4 — M=$(PWD) tells Kbuild: “now switch back to the module’s directory and build the modules there” |
| ↓ |
| Step 5 — Kbuild compiles your .c into .o using the kernel’s exact gcc flags, then links it with module glue code into .ko |
| ↓ |
Step 6 — your my_gpio_driver.ko is ready in the current directory |
This recursive structure — where your Makefile hands control to the kernel’s Makefile which then comes back to your directory — is the defining characteristic of out-of-tree module builds. It ensures binary compatibility between your module and the kernel.
5. The Kernel Headers Package
The path /lib/modules/$(uname -r)/build is not the full kernel source tree. It is a much smaller kernel headers package installed by your Linux distribution. It contains:
- The top-level kernel Makefile (with all compiler flags)
- Architecture-specific headers and configuration
- The
.configfile recording every Kconfig option set during the kernel’s build - The
Module.symversfile listing all exported kernel symbols and their checksums
It does not include the full kernel C source code. You only need that if you are patching or rebuilding the kernel itself.
# Install kernel headers on Debian / Ubuntu
sudo apt install linux-headers-$(uname -r)
# Install kernel headers on Fedora / RHEL
sudo dnf install kernel-devel-$(uname -r)
# Verify the headers directory exists
ls /lib/modules/$(uname -r)/build
6. Building a Module With Multiple Source Files
Real drivers are rarely a single file. When your module spans multiple .c files, Kbuild provides a clean way to specify all of them. You define the final module name and list the object files that should be linked together to form it:
# Makefile for a multi-file kernel module
# This builds my_spi_driver.ko from three .c files
obj-m += my_spi_driver.o
# Tell Kbuild which .o files make up my_spi_driver.o
my_spi_driver-objs := spi_core.o spi_dma.o spi_irq.o
KERNEL_DIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
$(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KERNEL_DIR) M=$(PWD) clean
With this setup, Kbuild compiles spi_core.c, spi_dma.c, and spi_irq.c individually into object files, then links them into a single my_spi_driver.o, and finally produces my_spi_driver.ko.
7. Understanding the Generated Build Files
After a successful build, your module directory contains several files besides the .ko:
| File | What it is |
|---|---|
| my_driver.ko | The final loadable kernel module — the file you pass to insmod |
| my_driver.o | Compiled object file before module linking stage |
| my_driver.mod.c | Auto-generated C file with module metadata (license, version magic, dependencies) |
| my_driver.mod.o | Compiled version of the auto-generated metadata file |
| Module.symvers | Symbol version checksums — used if your module exports symbols for other modules |
| modules.order | Order in which modules were built — used by modprobe |
| .my_driver.ko.cmd | Hidden file: the exact linker command used to create the .ko (useful for debugging build issues) |
Run make clean to remove all generated files except your original .c source and your Makefile.
8. Loading and Unloading Your Module
# Load your module
sudo insmod ./my_gpio_driver.ko
# Verify it loaded — check the kernel log
dmesg | tail -20
# List all currently loaded modules
lsmod | grep my_gpio
# View module information (license, author, description, parameters)
modinfo ./my_gpio_driver.ko
# Unload the module
sudo rmmod my_gpio_driver
# Verify it unloaded
dmesg | tail -5
insmod loads a module directly from a path you specify. modprobe is smarter — it searches the system’s module directories, automatically resolves dependencies, and loads any required modules first. For development use insmod; for production deployment use modprobe after running make install and depmod -a.
9. Version Magic — Why Your Module May Refuse to Load
Every kernel module compiled by Kbuild gets a vermagic string embedded in it. This string encodes the exact kernel version, SMP support status, preemption model, and other build attributes. When you try to load a module, the kernel checks this string against its own version. If they do not match exactly, the kernel refuses to load the module.
# See the vermagic string embedded in your .ko file
modinfo my_gpio_driver.ko | grep vermagic
# Example output:
# vermagic: 6.8.0-45-generic SMP preempt mod_unload modversions aarch64
Interview Questions & Answers
obj-y contains a list of object files to compile and link permanently into the kernel image (vmlinux). These subsystems or drivers are always present in memory. obj-m contains object files to compile as separate loadable kernel modules (.ko files) that can be inserted and removed at runtime. The letters match the Kconfig selection: Y for built-in, M for module.-C flag causes make to change into the kernel headers directory first and parse the kernel’s own top-level Makefile. This loads all the exact compiler flags, architecture settings, ABI conventions, and exported symbol checksums that were used when the running kernel was compiled. Once those are loaded, Kbuild switches back to the module directory (via the M= variable) to compile the module. This guarantees binary compatibility between the module and the running kernel..ko file that encodes the exact kernel version, SMP configuration, preemption model, and build flags. When loading a module, the kernel compares this string against its own. A module compiled for kernel 6.8.0-45 will be rejected on 6.8.0-47 because the vermagic strings differ. The solution is to recompile the module against the new kernel headers each time the kernel is updated.obj-m. Then you use a separate variable named modulename-objs to list all the individual object files that should be linked together. Kbuild compiles each .c file independently, links the resulting .o files into a single combined object, and produces one final .ko file.insmod loads a module directly from a file path you specify but does not handle dependencies — if your module depends on another module that is not yet loaded, insmod will fail. modprobe reads the dependency database generated by depmod and automatically loads any required modules first. For development, insmod is convenient. For production deployment, modprobe is correct and should be used after installing the module with make install and rebuilding the module dependency database with depmod -a.Free Linux Kernel & Device Driver Course
You have completed the printk and Kbuild trilogy. Continue with the full EmbeddedPathashala Linux kernel programming course.
← Start from Part 1 ← Part 2: Format Specifiers