all
Stage 08

Kernel Modules

Master the Linux kernel module lifecycle — init/exit macros, module parameters, symbol exports, Makefiles, insmod/modprobe/depmod, and writing your first loadable kernel module.

8 min read
45723 chars

Basic: Module Anatomy {:.gc-basic}

Basic

A Linux kernel module is a compiled object file that can be loaded into a running kernel without rebooting. The module infrastructure provides two mandatory hooks — module_init and module_exit — that the kernel calls when loading and unloading.

// hello.c — minimal kernel module
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name <you@example.com>");
MODULE_DESCRIPTION("Hello World kernel module");
MODULE_VERSION("1.0");

static int __init hello_init(void)
{
    pr_info("hello: module loaded\n");
    return 0;  /* non-zero means init failed */
}

static void __exit hello_exit(void)
{
    pr_info("hello: module unloaded\n");
}

module_init(hello_init);
module_exit(hello_exit);

The __init and __exit attributes place the functions in special ELF sections. After the module initialises successfully, __init memory is freed. __exit is discarded entirely for built-in code (modules compiled into the kernel) since it can never be unloaded.

Module metadata macros

Macro Purpose
MODULE_LICENSE("GPL") Required — unlocks GPL-only symbols
MODULE_AUTHOR(...) Displayed by modinfo
MODULE_DESCRIPTION(...) One-line summary
MODULE_VERSION(...) Arbitrary version string
MODULE_ALIAS(...) Additional module aliases

Out-of-tree Makefile

# Makefile — place alongside hello.c
obj-m += hello.o

# For a multi-file module:
# mydrv-objs := main.o helper.o
# obj-m += mydrv.o

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

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

clean:
	$(MAKE) -C $(KDIR) M=$(PWD) clean
# Build, load, inspect, unload
make
sudo insmod hello.ko
dmesg | tail -3
lsmod | grep hello
modinfo hello.ko
sudo rmmod hello

lsmod reads /proc/modules. modinfo parses the .modinfo ELF section embedded during compilation.


Module Parameters {:.gc-basic}

Basic

Module parameters let you tune behaviour at load time without recompiling. They are declared with module_param() in the global scope.

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>

static int   irq_num   = 17;
static bool  verbose   = false;
static char *dev_name  = "mydev0";
static int   counts[4];
static int   ncounts;

module_param(irq_num,  int,   0644);
MODULE_PARM_DESC(irq_num, "Hardware IRQ number (default 17)");

module_param(verbose,  bool,  0444);
MODULE_PARM_DESC(verbose, "Enable verbose logging (default false)");

module_param(dev_name, charp, 0444);
MODULE_PARM_DESC(dev_name, "Device name string (default mydev0)");

module_param_array(counts, int, &ncounts, 0644);
MODULE_PARM_DESC(counts, "Array of up to 4 count values");

static int __init param_init(void)
{
    int i;
    pr_info("irq_num=%d verbose=%d dev_name=%s\n",
            irq_num, verbose, dev_name);
    for (i = 0; i < ncounts; i++)
        pr_info("  counts[%d] = %d\n", i, counts[i]);
    return 0;
}
module_init(param_init);

Supported types for module_param

Type keyword C type
bool bool
invbool bool (logic inverted)
int int
uint unsigned int
short short
ushort unsigned short
long long
ulong unsigned long
charp char * (kernel copies string)
hexint unsigned int (hex input accepted)

The third argument to module_param is the sysfs permission bits. Setting 0 hides the parameter from sysfs entirely. When non-zero, the parameter appears at /sys/module/<name>/parameters/<param> and can be changed at runtime if the permission allows writes.

# Load with parameter overrides
sudo insmod param.ko irq_num=22 verbose=1 counts=10,20,30

# Read/write at runtime via sysfs
cat /sys/module/param/parameters/irq_num
echo 25 | sudo tee /sys/module/param/parameters/irq_num

Module Dependencies and modprobe {:.gc-mid}

Intermediate

insmod loads a single .ko file but does not resolve dependencies. modprobe consults the dependency database built by depmod.

# Rebuild the module dependency database (run as root after installing modules)
depmod -a

# depmod reads all .ko files under /lib/modules/$(uname -r)/
# and writes /lib/modules/$(uname -r)/modules.dep (text) and
# modules.dep.bin (binary, faster to parse)

# modprobe: loads module and all dependencies
sudo modprobe snd_hda_intel

# modprobe: remove module and unused dependencies
sudo modprobe -r snd_hda_intel

# insmod: load specific .ko, no dependency resolution
sudo insmod /path/to/mymodule.ko

# Show detailed module information
modinfo e1000e

modinfo output fields

filename:       /lib/modules/6.1.0/kernel/drivers/net/ethernet/intel/e1000e/e1000e.ko
description:    Intel(R) PRO/1000 Network Driver
author:         Intel Corporation
license:        GPL v2
version:        3.8.7
alias:          pci:v00008086d...
depends:        ptp
retpoline:      Y
intree:         Y
vermagic:       6.1.0 SMP mod_unload

Autoloading configuration

# /etc/modules-load.d/mymodule.conf — load at boot
mymodule

# /etc/modprobe.d/mymodule.conf — set default parameters
options mymodule irq_num=22 verbose=1

# Blacklist a module (prevent autoload)
# /etc/modprobe.d/blacklist.conf
blacklist nouveau

# Soft dependency (load snd_pcm before snd_hda_intel if available)
softdep snd_hda_intel pre: snd_pcm

Dependency types

Mechanism File Effect
depends: modules.dep Hard load-order dependency
softdep pre: modprobe.d Load listed modules before, if available
softdep post: modprobe.d Load listed modules after, if available
alias modules.alias Maps device IDs to module name

Symbol Exports {:.gc-mid}

Intermediate

Modules exist in separate namespaces. To call a function defined in another module, the exporting module must explicitly publish the symbol using EXPORT_SYMBOL or EXPORT_SYMBOL_GPL.

/* exporter.c — publishes helper functions */
#include <linux/module.h>
#include <linux/export.h>

int mylib_add(int a, int b)
{
    return a + b;
}
EXPORT_SYMBOL(mylib_add);       /* available to all modules */

int mylib_secret(int x)
{
    return x * 42;
}
EXPORT_SYMBOL_GPL(mylib_secret); /* GPL-licensed modules only */

static int __init exporter_init(void) { return 0; }
static void __exit exporter_exit(void) {}
module_init(exporter_init);
module_exit(exporter_exit);
MODULE_LICENSE("GPL");
/* consumer.c — calls functions from exporter */
#include <linux/module.h>
#include <linux/kernel.h>

extern int mylib_add(int a, int b);
extern int mylib_secret(int x);

static int __init consumer_init(void)
{
    pr_info("add(3,4) = %d\n", mylib_add(3, 4));
    pr_info("secret(5) = %d\n", mylib_secret(5));
    return 0;
}
module_init(consumer_init);
MODULE_LICENSE("GPL");
# Load exporter first, then consumer
sudo insmod exporter.ko
sudo insmod consumer.ko

# Verify exported symbols in the running kernel
grep mylib /proc/kallsyms

# All exported symbols from all modules
cat /proc/kallsyms | grep " T "   # text (code) symbols

EXPORT_SYMBOL vs EXPORT_SYMBOL_GPL

EXPORT_SYMBOL_GPL enforces that the consuming module declares MODULE_LICENSE("GPL") (or compatible). The kernel checks this at insmod time. Attempting to load a proprietary module that uses a GPL-only symbol results in:

ERROR: module consumer uses GPL-only symbols

Symbol versioning (CONFIG_MODVERSIONS)

When enabled, the kernel computes a CRC of each exported symbol’s type signature and embeds it in the module. A version mismatch between the running kernel’s symbol CRC and the module’s expected CRC causes load failure, preventing ABI breakage silently.


Advanced: Kernel Build System Integration {:.gc-adv}

Advanced

In-tree modules live under drivers/ (or sound/, net/, etc.) and are integrated with Kconfig and the kernel’s Makefile hierarchy.

# drivers/misc/mydev/Kconfig
config MYDEV
    tristate "My custom device driver"
    depends on HAS_IOMEM
    select REGMAP_I2C
    help
      Say Y to build this driver into the kernel,
      M to build as a loadable module, or N to skip.
# drivers/misc/mydev/Makefile
obj-$(CONFIG_MYDEV) += mydev.o
mydev-objs := main.o helper.o irq.o
# drivers/misc/Makefile — add one line
obj-$(CONFIG_MYDEV_DIR) += mydev/

Kconfig tristate values

Value Meaning
y Built into vmlinux
m Compiled as .ko loadable module
n Not compiled at all

Cross-compiling out-of-tree modules

# Makefile for ARM64 cross-compilation
ARCH    ?= arm64
CROSS_COMPILE ?= aarch64-linux-gnu-
KDIR    ?= /opt/linux-src

obj-m += mydrv.o

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

install:
	$(MAKE) -C $(KDIR) M=$(PWD) \
	    ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) \
	    INSTALL_MOD_PATH=/opt/rootfs modules_install

DKMS (Dynamic Kernel Module Support)

DKMS automatically recompiles out-of-tree modules when a new kernel is installed — essential for distribution packages.

# /usr/src/mydrv-1.0/dkms.conf
PACKAGE_NAME="mydrv"
PACKAGE_VERSION="1.0"
BUILT_MODULE_NAME[0]="mydrv"
BUILT_MODULE_LOCATION[0]="."
DEST_MODULE_LOCATION[0]="/updates/dkms"
AUTOINSTALL="yes"
MAKE="make -C ${kernel_source_dir} M=${dkms_tree}/${PACKAGE_NAME}/${PACKAGE_VERSION}/build"
CLEAN="make clean"

# Register and build
dkms add -m mydrv -v 1.0
dkms build -m mydrv -v 1.0 -k $(uname -r)
dkms install -m mydrv -v 1.0 -k $(uname -r)

Interview Q&A {:.gc-iq}

Interview Q&A

Q1: When exactly does module_init run relative to kernel boot?

For built-in code (obj-y), the __init functions are called by do_initcalls() during boot, ordered by initcall level (core_initcall, device_initcall, etc.). For loadable modules (.ko), module_init runs inside sys_init_module the moment insmod/modprobe loads the module into a running kernel — this can be any time after boot.

Q2: Why does EXPORT_SYMBOL_GPL actually enforce the GPL?

The kernel checks the license field embedded in the .modinfo section at load time. If a module calls a GPL-only symbol but declares a non-GPL license, load_module() rejects it with -EPERM. This is a legal enforcement mechanism — a proprietary module must not link against GPL internals.

Q3: Why does modprobe need modules.dep? Why can’t it just scan .ko files?

Scanning thousands of .ko files on every modprobe invocation would be too slow. depmod does this scan once at install time and writes modules.dep (a simple text makefile-style dependency graph). modprobe reads this cached file to determine load order in milliseconds.

Q4: What is the difference between insmod and modprobe?

insmod takes a filesystem path to a .ko file and loads it directly without resolving dependencies. If a required symbol is not present in the kernel, insmod fails. modprobe takes a module name, looks it up in modules.dep, loads all dependencies first, then loads the requested module. modprobe also reads /etc/modprobe.d/ for options and aliases.

Q5: What makes a kernel “tainted” and why does it matter?

A tainted kernel has had non-GPL or out-of-tree modules loaded, or has had other anomalous events (oops, unsigned module, etc.). The taint flags are visible in /proc/sys/kernel/tainted and printed in kernel crash messages. Kernel developers may refuse to debug issues on tainted kernels because the proprietary or out-of-tree code could be responsible.

Q6: How do you pass parameters to a module at load time?

# Via insmod (key=value after the .ko path)
sudo insmod mydrv.ko irq_num=22 verbose=1

# Via modprobe (reads /etc/modprobe.d/ or command line)
sudo modprobe mydrv irq_num=22 verbose=1

# Permanently via /etc/modprobe.d/mydrv.conf
echo "options mydrv irq_num=22 verbose=1" | sudo tee /etc/modprobe.d/mydrv.conf

Q7: What does vermagic mean in modinfo output?

vermagic is a string embedded in every module that encodes the kernel version, SMP configuration, preemption model, and other ABI-significant settings. The running kernel checks this string against its own at load time. A mismatch causes insmod to fail with invalid module format. You cannot load a module built for kernel 5.15 into kernel 6.1 without recompilation.


References {:.gc-ref}

References

Resource Link
Linux Kernel Module Programming Guide https://sysprog21.github.io/lkmpg/
kernel.org: kbuild modules documentation https://www.kernel.org/doc/html/latest/kbuild/modules.html
kernel.org: kernel parameters https://www.kernel.org/doc/html/latest/admin-guide/kernel-parameters.html
man 8 insmod https://linux.die.net/man/8/insmod
man 8 modprobe https://linux.die.net/man/8/modprobe
man 8 modinfo https://linux.die.net/man/8/modinfo
man 8 depmod https://linux.die.net/man/8/depmod
DKMS documentation https://github.com/dell/dkms
Kernel Symbol Namespaces https://www.kernel.org/doc/html/latest/core-api/symbol-namespaces.html