Building and Loading Kernel Modules
Compile a Linux driver out-of-tree, wire it into the kernel source tree, and load it safely with insmod, modprobe and depmod on a modern kernel.
Free linux kernel development course learners often write correct driver code and then get stuck at the very next step: turning that C file into something the kernel will actually run. This lecture is part of our free linux device drivers course and covers the two ways to build a driver — out-of-tree as a standalone module, and in-tree as part of the kernel source — and then the full lifecycle of loading, inspecting and removing that module safely. If you’re following this free embedded linux course sequentially, this picks up right after you’ve written your first character device driver skeleton.
What You Will Learn
Prerequisites
This lecture assumes you already have a buildable kernel source tree for your target (or a matching linux-headers package for your host, if you’re building for the machine you’re sitting at), and that you’ve written a minimal character device driver in a previous lecture of this free linux kernel development course. You should be comfortable with basic Makefile syntax and running commands as root on a Linux target.
Two Ways to Build a Driver
A Linux driver reaches the kernel one of two ways. It can live outside the kernel source tree entirely and be compiled as a loadable kernel module (an .ko file) using the kernel’s own build machinery — this is called an out-of-tree or “external module” build. Or it can live inside the kernel source tree itself, alongside drivers like drivers/char or drivers/net, and be built as part of a full kernel build or as a module selected through Kconfig.
Neither approach recompiles the driver from scratch with a plain gcc command. Both go through Kbuild, the kernel’s own recursive Makefile system, because a kernel module has to be compiled with the exact same compiler flags, structure layout, and ABI as the running kernel. A module built against the wrong kernel version, or with a mismatched CONFIG_ option, will simply refuse to load.
Building Out-of-Tree with Kbuild
An out-of-tree module needs a tiny Makefile that does almost nothing itself — it just hands control to the real kernel build system, pointed at your kernel’s build directory. On a development host targeting the host’s own running kernel, that directory is normally /lib/modules/$(uname -r)/build, a symlink into the matching kernel headers package. When cross-compiling for an embedded target, point it at your configured and built kernel source tree instead.
# Makefile
obj-m += ep_modtest.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
The obj-m += ep_modtest.o line is a Kbuild directive, not a normal Makefile rule — it tells Kbuild “there is a module target here, build it from ep_modtest.c.” The -C $(KDIR) switches make into the kernel source tree first, and M=$(PWD) tells that outer Makefile to come back into your directory to actually build the module. This two-step indirection is exactly what makes the module get built with the running kernel’s configuration and compiler flags.
Here is a minimal driver to build against that Makefile — deliberately small, just enough to prove the build and load pipeline works end to end:
// ep_modtest.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
static int __init ep_modtest_init(void)
{
pr_info("ep_modtest: loaded\n");
return 0;
}
static void __exit ep_modtest_exit(void)
{
pr_info("ep_modtest: unloaded\n");
}
module_init(ep_modtest_init);
module_exit(ep_modtest_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeddedPathashala");
MODULE_DESCRIPTION("Minimal module build/load demo");
$ make
make -C /lib/modules/6.11.0-generic/build M=/home/dev/ep_modtest modules
CC [M] ep_modtest.o
MODPOST Module.symvers
CC [M] ep_modtest.mod.o
LD [M] ep_modtest.ko
$ ls
ep_modtest.c ep_modtest.ko ep_modtest.mod ep_modtest.mod.c
ep_modtest.mod.o ep_modtest.o Module.symvers modules.order Makefile
Notice all the extra files: Module.symvers records exported symbol versions, modules.order lists build order for multi-module projects, and *.mod.c/*.mod.o hold the auto-generated module metadata (license, parameters, module info) that Kbuild compiles in separately. Only ep_modtest.ko matters for loading; the rest are build artifacts you can safely make clean away.
Building In-Tree
If the driver is meant to ship as part of a board’s kernel rather than as a standalone module, it belongs inside the kernel source tree, in the directory matching its subsystem — a simple character device would go under drivers/char, for example. Two lines are enough to hook it into the existing Kbuild Makefile for that directory:
# drivers/char/Makefile
obj-m += ep_modtest.o # always built as a loadable module
# or
obj-y += ep_modtest.o # always built directly into the kernel image
Hard-coding obj-y or obj-m means the driver is always built one way or the other, for every board using that Makefile. In real kernel trees this is almost always done conditionally instead, driven by a Kconfig option so the driver can be turned on or off per board configuration:
# drivers/char/Kconfig
config EP_MODTEST
tristate "EP module build/load demo driver"
default n
help
Minimal demo driver used to illustrate kernel module
build and load mechanics. Say M to build as a module.
# drivers/char/Makefile
obj-$(CONFIG_EP_MODTEST) += ep_modtest.o
With tristate, running make menuconfig lets you pick y (built-in), m (module), or leave it out entirely — obj-$(CONFIG_EP_MODTEST) then expands to obj-y, obj-m, or nothing at all, depending on what was selected during configuration.
Loading and Unloading Modules
Once you have a .ko file, four small utilities from kmod cover almost everything you need day to day:
| Command | Purpose |
|---|---|
insmod | Load a module from an explicit .ko path, no dependency resolution |
rmmod | Unload a module by name |
lsmod | List currently loaded modules, their size, and use count |
modinfo | Inspect a .ko file’s metadata without loading it |
$ sudo insmod ./ep_modtest.ko
$ dmesg | tail -n 2
[ 1942.113092] ep_modtest: loaded
$ lsmod | grep ep_modtest
ep_modtest 16384 0
$ modinfo ep_modtest.ko
filename: /home/dev/ep_modtest/ep_modtest.ko
description: Minimal module build/load demo
author: EmbeddedPathashala
license: GPL
srcversion: 4C3A9E9F2B1D7E6ABCF1234
depends:
vermagic: 6.11.0-generic SMP preempt mod_unload
$ sudo rmmod ep_modtest
$ dmesg | tail -n 1
[ 1958.501774] ep_modtest: unloaded
The vermagic line in modinfo output is what enforces the “only loads on the kernel it was built against” rule — insmod checks it against the running kernel’s version string and refuses to load on a mismatch, which is by far the most common “why won’t my module load” problem for beginners.
Installing Modules Properly with depmod and modprobe
insmod is fine for a module you just built and are testing by hand, but it doesn’t understand dependencies between modules, and it needs the exact file path every time. For anything installed permanently, modules are copied into /lib/modules/<kernel-release>/ (often under a subdirectory such as extra/ or updates/), and then depmod is run to (re)build the dependency and alias databases that live alongside them:
$ sudo cp ep_modtest.ko /lib/modules/$(uname -r)/extra/
$ sudo depmod -a
$ ls /lib/modules/$(uname -r)/
kernel modules.alias modules.alias.bin modules.builtin
modules.builtin.bin modules.dep modules.dep.bin modules.devname
modules.order modules.softdep modules.symbols modules.symbols.bin
With that database in place, modprobe replaces insmod/rmmod for everyday use — it looks the module up by name instead of by path, pulls in any modules it depends on automatically, and can also remove a module along with any dependents nobody else still needs:
$ sudo modprobe ep_modtest
$ sudo modprobe -r ep_modtest
This dependency database is also what powers automatic module loading. When new hardware appears — a USB device being plugged in is the classic example — the kernel raises a uevent, udevd picks it up, reads the vendor and product identifiers the hardware reports, and searches modules.alias for a module whose declared device table matches. If it finds one, it calls modprobe for you, and the right driver loads with no user action at all. This only works for drivers that declare their supported devices with MODULE_DEVICE_TABLE(), a topic covered in full in the next lecture on device trees and platform data.
Common Mistakes and Troubleshooting
- “Invalid module format” on insmod — almost always a
vermagicmismatch; rebuild against the exact running kernel’sKDIR, don’t reuse a.koacross kernel versions. - Module builds but nothing shows in dmesg — check
dmesgpermissions/log level rather than assuming the module didn’t load; confirm withlsmodfirst. - “Unknown symbol” at load time — the module depends on a symbol exported by another module that isn’t loaded yet; check
depends:inmodinfooutput and load that module (or letmodprobedo it for you) first. - Forgetting
depmod -aafter copying a module into/lib/modules/—modprobewill report “module not found” even though the file is right there, because the alias database hasn’t been regenerated.
Best Practices
- Always set
MODULE_LICENSE()honestly — an incorrect or missing license string taints the kernel and disables access to GPL-only exported symbols. - Prefer
modprobeoverinsmod/rmmodfor anything beyond quick iteration during development, so dependency handling stays automatic. - Use a Kconfig
tristateoption rather than hard-codedobj-y/obj-mfor any in-tree driver so downstream board configurations can opt in or out. - Keep a clean
make cleanstep in out-of-tree Makefiles — stray build artifacts from a previous kernel version are a frequent source of confusing build errors.
Summary
Every Linux driver, whether it lives permanently in the kernel source tree or is built as a standalone module, goes through the same Kbuild engine so that it stays binary-compatible with the exact kernel it will run on. Out-of-tree builds use a thin Makefile that redirects into the kernel’s build directory; in-tree builds hook into an existing subsystem Makefile, usually gated by a Kconfig tristate option. Once built, insmod/rmmod give direct, dependency-free control for quick testing, while depmod plus modprobe is the production path that resolves dependencies and enables udev-driven automatic loading. With that build-and-load pipeline solid, the next lecture moves on to how drivers actually get matched to hardware in the first place, through device trees and platform data.
FAQ
Why does my module fail to load with “invalid module format”?
This is almost always a kernel version mismatch — the module’s embedded vermagic string doesn’t match the running kernel. Rebuild the module against /lib/modules/$(uname -r)/build for the kernel you’re actually loading into.
What’s the difference between insmod and modprobe?
insmod loads a single .ko file from an explicit path with no dependency resolution. modprobe looks a module up by name using the depmod-generated database, automatically loading any modules it depends on first.
Do I need to run depmod every time I build a module?
Only when you install or update a module inside /lib/modules/<kernel-release>/. Modules loaded directly by path with insmod during development don’t need it.
What does obj-m vs obj-y actually control?
obj-m builds the source file as a separate loadable .ko module; obj-y links it directly into the kernel image (vmlinux) as a built-in driver. A Kconfig tristate option lets a board choose between the two, or neither.
Why do I get “unknown symbol” errors when loading my module?
Your module references a symbol exported by another module (via EXPORT_SYMBOL/EXPORT_SYMBOL_GPL) that isn’t currently loaded. Check the depends: field from modinfo and load that module first, or use modprobe which resolves this automatically.
Can a module I built on my PC run on my embedded board?
Only if it was cross-compiled against that board’s exact kernel source tree and configuration. A module built against your PC’s kernel headers will not load on a different architecture or kernel version.
What’s the point of MODULE_LICENSE?
It declares the module’s license to the kernel at load time. An incorrect or missing GPL-compatible license “taints” the kernel and blocks access to symbols exported with EXPORT_SYMBOL_GPL.
Ready to Match Drivers to Hardware?
Next up in this free linux device drivers course: how the kernel discovers hardware and links it to the right driver using device trees and platform data.
Continue to Next Lecture Back to Course Index
3 Comments