Kernel Modules
Loading, unloading, parameters, dependency resolution, taint, and building a module out of tree.
A module is an object file the kernel links into itself at runtime. Writing one is the shortest path from reading about the kernel to running code inside it, and the whole lifecycle — build, load, observe, unload — fits on one page.
The minimum module
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
static int __init hello_init(void)
{
pr_info("hello: loaded\n");
return 0;
}
static void __exit hello_exit(void)
{
pr_info("hello: unloaded\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("you");
MODULE_DESCRIPTION("Minimal example module");
module_init/module_exit register the functions the kernel calls on load and unload;
MODULE_LICENSE/MODULE_AUTHOR/MODULE_DESCRIPTION are metadata macros that write into a dedicated
section of the compiled object, readable later with modinfo. Building it out of tree needs a four-line
Makefile that hands the work to the installed kernel's own build system instead of reimplementing it:
obj-m += hello.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
-C switches make into the running kernel's build tree; M=$(PWD) tells that tree's modules target to
come back and build only the object named by obj-m in this directory, not the kernel itself.
MODULE_LICENSE and taint
MODULE_LICENSE is not a formality. The string it declares is checked at load time: a license the kernel
does not recognise as GPL-compatible sets a taint flag on the running kernel and restricts what symbols the
module can resolve (see exported symbols). A tainted kernel is
still running, but the taint is now part of any bug report — maintainers reading a crash dump see it and
weight the report accordingly, because non-GPL code they cannot inspect may be involved. The current taint
state is one file away:
$ cat /proc/sys/kernel/tainted
4096
The value is a bitmask; individual bits mean things like "a proprietary module is loaded", "a module was
force-loaded", or "the machine has an unsigned module loaded" — dmesg around the load usually names the
bit in plain text (for example, "module license 'X' taints kernel"), which is faster to read than
decoding the mask by hand.
Parameters
module_param(name, type, perm) declares a variable the module accepts at load time
(insmod hello.ko debug=1) and, if perm is non-zero, exposes as a file under
/sys/module/hello/parameters/. A non-zero permission mode is a promise about more than default value: a
parameter file opened for writing lets a value be changed on a module that is already loaded and running,
without a reload — useful for a debug flag, and a real hazard for anything the module's init path assumed
was fixed for the module's lifetime.
insmod versus modprobe
insmod takes a path to a .ko file and does exactly what it's told: no dependency resolution, no
configuration files consulted, nothing loaded but the one file named. modprobe is the tool built on top of
it that resolves a module's dependencies from modules.dep (generated by depmod), loads them in the
right order, and honours /etc/modprobe.d/ for blacklists and options — which is why modprobe is what you
almost always want, and insmod is what you reach for only when loading one exact, already-built .ko by
hand.
Where the kernel keeps it
/proc/modulesandlsmod(a thin formatter over the same data) — one line per loaded module: name, size, and a reference count with the list of modules or kernel facilities holding a reference./sys/module/<name>/— a directory per loaded module, holdingparameters/,refcnt,sections/, and (for a module built with debug info retained).text,.data, and similar addresses used by tools that symbolicate kernel addresses.
The reference count (refcnt) is the number that decides whether a module can be removed at all: it is
incremented by every kernel facility currently depending on the module — another loaded module calling its
exported symbols, an open device file backed by it, a mounted filesystem it implements — and rmmod refuses
to proceed while it is non-zero.
Unloading, and why it often fails
Two things stop rmmod from succeeding: a non-zero reference count (something is still using the module,
and removing it out from under that user would be removing code whose instructions might be executed a
moment later), or a module built without unload support at all (CONFIG_MODULE_UNLOAD off, or the module's
own code declines to register an exit function). Neither is a bug in rmmod — both are the kernel refusing
to do something it cannot do safely.
rmmod -f forces removal past a non-zero reference count. It does not make the code holding that reference
stop calling into the module — it just deletes the memory the next call would have jumped into. This is not
a way around a stuck reference count; it is a way to turn a "can't unload" situation into a use-after-free.
A module's lifecycle
A module's lifecycle: relocation and symbol resolution happen in loading, and unloading is only
reachable once every reference has been dropped.
- In the lab VM (or on the host, building against the lab kernel's build tree), create
hello.candMakefilewith the contents above. make— this cross-invokes the kernel build tree via-C ... M=$(PWD) modulesand produceshello.ko.insmod hello.ko, thendmesg | tail -2— the last line should behello: loaded.lsmod | grep hello— confirms it's loaded, withUsed byat0.rmmod hello, thendmesg | tail -1— the last line should behello: unloaded.
If step 3 fails with Invalid module format, the module was built against a different kernel's headers
than the one currently running — that mismatch, and how the kernel detects it, is the next page's topic.
References
- Tainted kernels — every taint bit and what it
tells a maintainer reading a bug report; the concrete reason
MODULE_LICENSEis not a formality. - Building external modules — the official out-of-tree build
procedure the
Makefileabove is taken from. man 8 modprobe— dependency resolution viamodules.depand the/etc/modprobe.d/configuration directory thatinsmoddoes not consult.