Writing and Loading Kernel Modules
The lifecycle of a kernel module from source to running code
A minimal kernel module
/* hello.c */
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
static int __init hello_init(void)
{
pr_info("Hello, kernel!\n"); /* prints to kernel log */
return 0; /* non-zero = load failed */
}
static void __exit hello_exit(void)
{
pr_info("Goodbye, kernel!\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Example Author");
MODULE_DESCRIPTION("A minimal hello world module");
MODULE_VERSION("1.0");
# Makefile
obj-m := hello.o
# If source is out-of-tree:
KDIR ?= /lib/modules/$(shell uname -r)/build
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
# Build
make
# Load
sudo insmod hello.ko
# Check output
dmesg | tail -3
# [12345.678] Hello, kernel!
# Unload
sudo rmmod hello
dmesg | tail -3
# [12346.789] Goodbye, kernel!
Module lifecycle
insmod/modprobe rmmod
│ │
▼ ▼
load_module() delete_module()
│ │
├── module_sig_check() ├── refcount must be 0
├── elf_validity_cache_copy() │ (try_stop_module)
├── early_mod_check() ├── mod->exit()
│ └── parse .modinfo, check vermagic │
├── layout_and_allocate() └── free_module()
│ └── layout_sections() └── free_mod_mem()
├── add_unformed_module() └── module_memory_free()
├── module_augment_kernel_taints() └── execmem_free(mem->base)
│ └── MODULE_LICENSE check (kernel taint set here)
├── find_module_sections() /* mod->syms, crcs, flagstab */
├── simplify_symbols() / apply_relocations()
├── complete_formation()
│ └── MODULE_STATE_COMING: module now visible in
│ /proc/modules; symbols exported to other modules
│
└── return do_init_module(mod); /* final statement of load_module() */
│ (runs mod->init)
├── return 0: MODULE_STATE_LIVE
└── return -errno: module unloaded immediately
Module sections
A module .ko file is an ELF with special sections:
objdump -h hello.ko | grep -E "(\.init|\.exit|\.text|\.data|\.rodata|__param|\.modinfo)"
# .init.text contains hello_init() (freed after module loads)
# .exit.text contains hello_exit() (kept until module unloads)
# .text normal code
# .data read-write data
# .rodata read-only data
# __param module_param definitions
# .modinfo MODULE_LICENSE, MODULE_AUTHOR, etc.
The __init attribute marks functions that should be freed after the module init runs (saving memory). Similarly __exit code can be discarded on permanent modules.
/proc/modules
lsmod
# Module Size Used by
# e1000e 262144 0
# ptp 28672 1 e1000e
cat /proc/modules
# e1000e 262144 0 - Live 0xffffffffc0400000 (illustrative only -- a
# stock distro-signed e1000e has no taint flags; (OE) would mean an
# out-of-tree, unsigned build)
# Field: name size refcount deps state address (flags)
# Flags: O=out-of-tree, E=unsigned, F=forced
# See module dependencies
cat /lib/modules/$(uname -r)/modules.dep | grep e1000e
The module struct in the kernel
/* include/linux/module.h */
struct module {
enum module_state state; /* MODULE_STATE_LIVE/COMING/GOING/UNFORMED */
struct list_head list; /* linked into modules list */
char name[MODULE_NAME_LEN];
struct module_kobject mkobj; /* /sys/module/name/ */
struct module_attribute *modinfo_attrs;
const char *version;
const char *srcversion; /* source hash */
const char *imported_namespaces; /* MODULE_IMPORT_NS() list */
struct kobject *holders_dir; /* /sys/module/name/holders/ */
/* Exported symbols — one table covers both plain and GPL-only
* exports; flagstab[i] holds the flag byte (KSYM_FLAG_GPL_ONLY)
* for syms[i]. There is no separate gpl_syms/gpl_crcs table. */
const struct kernel_symbol *syms;
const u32 *crcs;
const u8 *flagstab;
unsigned int num_syms;
/* Parameters: */
struct kernel_param *kp;
unsigned int num_kp;
/* Set once this module resolves any GPL-only symbol: */
bool using_gplonly_symbols;
/* Startup function (not under CONFIG_MODULE_UNLOAD): */
int (*init)(void);
/* Module memory: one entry per region — MOD_TEXT, MOD_DATA,
* MOD_RODATA, MOD_RO_AFTER_INIT, MOD_INIT_TEXT, MOD_INIT_DATA,
* MOD_INIT_RODATA. This replaced the older core_layout/init_layout
* pair of struct module_layout in 6.4; struct module_layout is gone. */
struct module_memory mem[MOD_MEM_NUM_TYPES] __module_memory_align;
/* Tracepoints: */
#ifdef CONFIG_TRACEPOINTS
unsigned int num_tracepoints;
tracepoint_ptr_t *tracepoints_ptrs;
#endif
#ifdef CONFIG_MODULE_UNLOAD
struct list_head source_list; /* modules that depend on me */
struct list_head target_list; /* modules I depend on */
void (*exit)(void); /* destruction function */
atomic_t refcnt; /* module reference count */
#endif
/* ... */
};
pr_* logging
/* Logging macros (prefer these over printk directly): */
pr_emerg("..."); /* KERN_EMERG — system is unusable */
pr_alert("..."); /* KERN_ALERT — action must be taken immediately */
pr_crit("..."); /* KERN_CRIT */
pr_err("..."); /* KERN_ERR — error conditions */
pr_warn("..."); /* KERN_WARNING */
pr_notice("..."); /* KERN_NOTICE */
pr_info("..."); /* KERN_INFO */
pr_debug("..."); /* KERN_DEBUG — compiled out unless CONFIG_DYNAMIC_DEBUG (runtime-switchable) or DEBUG is defined */
/* Device-specific logging (prefixes with device name): */
dev_err(&pdev->dev, "failed to allocate: %d\n", ret);
dev_info(&pdev->dev, "initialized at %px\n", base); /* %p alone prints a
hashed, useless
value since 4.15 */
/* Rate-limited logging: */
pr_info_ratelimited("too many events\n");
/* Dynamic debug: selectively enable at runtime */
/* echo "module hello +p" > /proc/dynamic_debug/control */
pr_debug("This only shows when enabled dynamically\n");
Module signing
Modern kernels can enforce that modules are signed by a trusted key:
# Check if kernel requires signed modules
cat /sys/module/module/parameters/sig_enforce # Y = unsigned modules rejected
# Unrelated kill switch: blocks ALL module loading *and* unloading once set
cat /proc/sys/kernel/modules_disabled # 0=allow, 1=no init_module/finit_module/delete_module (irreversible)
# Sign a module
/usr/src/linux-headers-$(uname -r)/scripts/sign-file \
sha256 signing_key.pem signing_key.x509 hello.ko
# Check module signature
modinfo hello.ko | grep sig
# Kernel boot params:
# module.sig_enforce=1 — require signature (any unsigned module is rejected)
Module debugging
# Dynamic debug: enable pr_debug() for a module
# Control file: /proc/dynamic_debug/control, or the equivalent
# /sys/kernel/debug/dynamic_debug/control when debugfs is enabled
echo "module hello +p" > /proc/dynamic_debug/control
echo "file hello.c +p" > /proc/dynamic_debug/control
echo "func hello_init +p" > /proc/dynamic_debug/control
# Show all dynamic debug settings
cat /proc/dynamic_debug/control
# KASAN for memory errors (CONFIG_KASAN=y)
# Load module and trigger bug → KASAN reports use-after-free, etc.
# Check module's kallsyms
grep hello /proc/kallsyms
# ffffffffc0401000 t hello_exit [hello]
# Note: hello_init does NOT show up here. do_init_module() switches
# mod->kallsyms over to core_kallsyms once init has run, and that table
# excludes every symbol living in an init-type section — so __init
# symbols vanish as soon as the module finishes loading.
# Crash debugging with gdb: the section base comes from sysfs, not from a
# symbol address (this module's only two functions are __init/__exit, so
# .text itself is empty and has no sysfs entry -- read .exit.text instead,
# since hello_exit is what's still resident and is what we're debugging)
cat /sys/module/hello/sections/.exit.text
# 0xffffffffc0401000
gdb vmlinux
(gdb) add-symbol-file /path/to/hello.ko 0xffffffffc0401000
(gdb) list hello_exit
Further reading
Kernel source
- kernel/module/main.c — the loader core:
load_module(),layout_sections(),do_init_module()(which calls the module'sinitfunction), andfree_module()on the unload path - include/linux/module.h —
struct moduleitself, plus themodule_init()/module_exit()andMODULE_LICENSE()/MODULE_AUTHOR()/MODULE_DESCRIPTION()/MODULE_VERSION()macros used by the hello-world example - include/linux/init.h — where
__initand__exitare defined as__section(".init.text")and__section(".exit.text"), the attributes behind the discardable-section behaviour described above - include/linux/moduleparam.h —
MODULE_INFO()emitting into the.modinfosection, andmodule_param()emitting into__param; the two sectionsobjdumpshows above - kernel/module/procfs.c — generates the
/proc/modulesline format: name, size, refcount, dependency list,Live/Loading/Unloadingstate, base address, and taint flags - include/linux/printk.h — the
pr_emerg()…pr_debug()family andpr_info_ratelimited()
Man pages
insmod(8)— the "trivial program to insert a module"; points readers atmodprobe(8), which is what handles module dependenciesrmmod(8)— unloading, and why-f/--forceneedsCONFIG_MODULE_FORCE_UNLOADmodprobe(8)— dependency-aware loading viamodules.dep.bin, and-rto remove a module plus its now-unused dependenciesmodinfo(8)— dumps the.modinfofields (license,author,description,parm,depends,alias,filename); note that kmod also prints thesig*signature fields, which this man page does not documentlsmod(8)— the formatted view of/proc/modulesshown abovedepmod(8)— builds themodules.depfile thatmodprobereads
Related pages
- Module Loading Internals — what
load_module()actually does between the ELF arriving andinitbeing called - Parameters, Symbols, and Kconfig —
module_param()andEXPORT_SYMBOL/EXPORT_SYMBOL_GPL - Module Signing — the signature-enforcement machinery sketched in the signing section above
- Kbuild — how
obj-mand the out-of-treeM=$PWDbuild actually work - Dynamic Debug — the full query syntax behind
echo "module hello +p" - Platform Drivers — most drivers are modules
- BPF Verifier — how the kernel admits untrusted extension code without the
.koload path: static verification instead of signature and symbol checks
LWN articles
- Two approaches to tightening restrictions on loadable modules — Jonathan Corbet, November 2024: why
MODULE_LICENSE("GPL")is a load-bearing declaration rather than a formality, covering the GPLv3-declared-as-GPL dispute and per-module symbol export namespaces - Yet another memory allocator for executable code — Jonathan Corbet, June 2023: how memory for module text is allocated, and the work replacing
module_alloc()with a shared executable-memory allocator
External
- Building External Modules — The Linux Kernel documentation — the upstream reference for the
obj-mandmake -C $KDIR M=$PWD modulespattern used in the Makefile above - Kernel module signing facility — The Linux Kernel documentation —
scripts/sign-file,module.sig_enforce=1, andCONFIG_MODULE_SIG_FORCE - Dynamic debug — The Linux Kernel documentation — the
module/file/funcquery language and+pflag for enablingpr_debug()at runtime - Message logging with printk — The Linux Kernel documentation — the
KERN_*log levels behind eachpr_*()macro, andpr_fmt()for prefixing a module's messages