What Is a Kernel Module?
The Linux kernel is monolithic by design: drivers, filesystems, and network protocols all execute in a single privileged address space rather than in isolated server processes. Without modules, every feature would have to be compiled directly into vmlinuz — and adding support for a new Wi-Fi adapter or filesystem would mean rebooting into a fresh kernel image.
A loadable kernel module (LKM) breaks that tie. A module is a relocatable ELF object — by convention a .ko file ("kernel object") sitting under /lib/modules/$(uname -r)/ — that the kernel can splice into its own address space at runtime. Once loaded it runs at the same privilege level as the rest of the kernel, calls the same internal APIs, and is indistinguishable from built-in code from the user's point of view. The only difference is that it arrived after boot.
This plugin model is what lets a stock distribution kernel support thousands of devices it has never seen. Modules let the kernel stay small in memory (load what you need, drop what you don't), but the trade-off is real: a malicious or buggy module has full access to kernel memory, so the loading path is one of the most carefully gated operations in Linux.
Module vs Built-in: The Trade-off
Every feature in the kernel configuration (make menuconfig) is one of three things: y (compiled into the kernel image), m (built as a module), or n (excluded entirely). The choice has real consequences for boot time, memory footprint, and whether the system can even start.
Understanding kernel configuration choices (y/m/n)
Compiled into vmlinuz
Adds ~200KB to kernel
Zero load time
Always in memory (~300KB)
Requires kernel recompile
Root filesystem (must be available at boot)
Separate .ko file
Not loaded at boot
~5ms to load
Only when mounted
Update without reboot
Secondary storage, external drives
Use built-in when the feature must be available before the root filesystem is mounted (root fs driver, essential boot hardware). Use modules for everything else—they keep your kernel smaller and more flexible.
When You Must Choose Built-in
Some code has to be in the kernel before any module can be loaded. The classic example is the driver for the disk that holds the root filesystem — you cannot load a module from a disk you do not yet know how to read. The same reasoning applies to early-boot console drivers, the filesystem driver for /, and any cryptographic algorithm needed to verify the initramfs.
Distributions sidestep this by shipping an initramfs — a tiny in-memory root filesystem packed into the kernel image at boot. Drivers needed to reach the real root (NVMe, RAID, LVM, dm-crypt) live there as modules and get loaded before the pivot to disk. The result is that even "essential" subsystems can ship as modules, as long as initramfs includes them.
When Modules Are the Right Default
Anything that is rarely used, hardware-specific, or experimental should be a module:
- Device drivers for hardware you may or may not have (Wi-Fi chipsets, GPUs, USB peripherals)
- Filesystems you only mount occasionally (NTFS, exFAT, NFS)
- Network protocols beyond TCP/IP (SCTP, DCCP, wireguard)
- Out-of-tree code that does not (or cannot) live in the mainline kernel
Modules also enable a debug-and-iterate workflow that built-ins do not. You can rmmod a misbehaving module, recompile, and insmod again — no reboot, no lost work.
The Module Lifecycle
A module is not just a blob of code. It has a defined entry point, an exit point, and a reference count that the kernel uses to decide whether unloading is safe.
Kernel modules are pieces of code that can be loaded and unloaded into the kernel on demand. They extend kernel functionality without requiring a reboot.
lsmodmodinfo module_nameinsmod module.komodprobe module_namermmod module_namedepmod -aInit and Exit
Every module declares two functions via the module_init() and module_exit() macros:
static int __init mymodule_init(void) { pr_info("mymodule: loading\n"); return 0; /* non-zero aborts the load */ } static void __exit mymodule_exit(void) { pr_info("mymodule: unloading\n"); } module_init(mymodule_init); module_exit(mymodule_exit); MODULE_LICENSE("GPL");
The __init annotation lets the kernel free that function's memory once the module is loaded — initialization code only runs once, so keeping it resident wastes pages. __exit lets the kernel skip the function entirely if the module is built statically (built-ins can never unload).
Reference Counts and Unload Safety
The kernel tracks how many things depend on a module: open file descriptors, mounted filesystems, registered subsystems. While that count is non-zero, rmmod refuses to unload — the module exports symbols or hooks that other code is actively using, and pulling it out would dereference freed memory.
You can inspect the count in /proc/modules (third column) or via lsmod (the Used by column). A module with 0 is safe to remove; anything higher means something else has it hooked.
Module Parameters
Modules can accept parameters at load time, exposed both as insmod arguments and as files under /sys/module/<name>/parameters/:
static int debug_level = 0; module_param(debug_level, int, 0644); MODULE_PARM_DESC(debug_level, "Enable debug output (0-3)");
insmod mymodule.ko debug_level=2 cat /sys/module/mymodule/parameters/debug_level # 2 echo 3 > /sys/module/mymodule/parameters/debug_level # tunable at runtime
Module Symbols and Dependencies
A module rarely lives alone. To call kmalloc(), register a netdev, or access a sysfs node, it needs functions defined elsewhere — in the core kernel or in another module.
EXPORT_SYMBOL and EXPORT_SYMBOL_GPL
The kernel does not export every internal function to modules. A symbol becomes visible to module code only when the defining file explicitly opts in:
int my_helper(int x) { return x + 1; } EXPORT_SYMBOL(my_helper); /* visible to any module */ int my_gpl_helper(int x) { return x * 2; } EXPORT_SYMBOL_GPL(my_gpl_helper); /* visible only to GPL-licensed modules */
The _GPL variant is a license enforcement mechanism: at load time the kernel checks the module's MODULE_LICENSE() declaration, and if a non-GPL module tries to resolve a GPL-only symbol, the load fails with Unknown symbol. This is how the kernel community gates proprietary modules out of GPL-only APIs.
You can list every exported symbol in a running kernel:
cat /proc/kallsyms | grep ' T ' # exported text symbols
depmod and the Dependency Graph
When modprobe foo runs, it does not just load foo.ko — it resolves the chain of modules foo depends on and loads them first. That chain is precomputed:
sudo depmod -a # rebuild /lib/modules/$(uname -r)/modules.dep
modules.dep is a plain-text manifest mapping each module to its prerequisites. modprobe reads it, walks the graph, and issues insmod calls in topological order. insmod itself is dumb — it loads one file and fails if any symbol is missing — which is why modprobe is almost always the right tool.
Module Security and Signing
A kernel module runs with full kernel privileges. If an attacker can load arbitrary code into the kernel, they own the machine. Modern Linux gates the load path through several layers, with Secure Boot module signing as the strictest.
How unsigned modules fail and how to sign them
Build module from source
Create Machine Owner Key pair
Sign with MOK private key
Import MOK into UEFI
Accept key in shim menu
Module loads successfully
Secure Boot requires all kernel modules to be signed with a trusted key. Click "Animate" to see the full signing process.
Machine Owner Key is your personal signing key that you enroll in UEFI. It extends the Secure Boot trust chain to include your own modules without disabling security.
Many distributions automatically sign DKMS modules (like VirtualBox, NVIDIA) during installation. If you use DKMS, signing is often handled for you.
How Signature Verification Works
When CONFIG_MODULE_SIG_FORCE=y (the default under Secure Boot), the kernel will refuse to load any module that is not signed by a key in its trusted keyring. The chain looks like this:
- The distribution builds the kernel with a public key embedded.
- Every module shipped in the kernel package is signed with the matching private key.
- At boot, Secure Boot in firmware verifies the kernel itself is signed by a key in the platform's UEFI db.
- At module load, the kernel verifies the
.koagainst its embedded keyring.
A locally compiled module is, by default, unsigned — which is why DKMS modules (NVIDIA, VirtualBox, ZFS) fail with Required key not available on Secure Boot systems unless extra steps are taken.
MOK: Adding Your Own Key
The Machine Owner Key (MOK) mechanism lets you enroll your own signing key into the firmware's trusted set without disabling Secure Boot:
# Generate a key pair openssl req -new -x509 -newkey rsa:2048 -keyout MOK.priv \ -outform DER -out MOK.der -nodes -days 36500 \ -subj "/CN=My Module Signing Key/" # Enroll the public half with shim (will prompt on next reboot) sudo mokutil --import MOK.der # Sign a module /usr/src/linux-headers-$(uname -r)/scripts/sign-file \ sha256 MOK.priv MOK.der mymodule.ko
After the next reboot, the shim bootloader presents a blue MOK enrollment screen; once the key is enrolled, any module signed with it loads cleanly.
The Tainted Kernel Mystery
Whenever the kernel is asked to do something unusual — load a proprietary module, take a manual panic(), run on unsupported hardware — it sets a bit in a global taint mask. The mask is exposed at /proc/sys/kernel/tainted, and kernel developers use it to triage bug reports: a tainted kernel is, by definition, in a state the community cannot fully reason about.
Understanding what kernel taint flags mean
Proprietary module loaded
Example: nvidia, broadcom-wl
Out-of-tree module loaded
Example: vboxdrv, zfs, custom modules
- • Kernel bug reports may be ignored
- • Support from distro vendors limited
- • Some features may refuse to work
- • Debugging becomes harder
- • P - nvidia/broadcom drivers (normal)
- • O - VirtualBox, ZFS (expected)
- • E - DKMS modules (typical)
- • I - ACPI workarounds (benign)
Seeing P+O+E on a desktop with NVIDIA graphics and VirtualBox is completely normal. Kernel developers only worry about taint flags when debugging kernel crashes—for daily use, these are fine.
Common Taint Flags
Each bit in the mask corresponds to a specific taint reason. The most common ones on a working desktop:
| Bit | Letter | Meaning | Typical Cause |
|---|---|---|---|
| 0 | G/P | Proprietary module loaded | NVIDIA, VirtualBox, vendor drivers |
| 1 | F | Module force-loaded (insmod -f) | Version mismatch override |
| 2 | S | Kernel running on unsupported SMP config | Old hardware |
| 4 | M | Machine Check Exception occurred | CPU hardware error |
| 9 | A | ACPI table overridden | Custom DSDT for laptops |
| 12 | O | Out-of-tree module loaded | Anything not in mainline |
| 13 | E | Module with unsigned signature | Local builds without MOK enrollment |
| 14 | K | Kernel live-patched | Ksplice, kpatch, livepatch |
A reading of P+O+E (or numerically, 12289) is unremarkable on a system running a third-party driver. A reading that includes M (Machine Check) or D (kernel died) is a signal that something hardware-level or fatal has happened and warrants investigation.
Why It Matters
Taint flags do not break anything by themselves — they are a diagnostic breadcrumb. Their real purpose is in the dmesg of an oops or panic: if a bug report includes Tainted: P, kernel maintainers know proprietary code was running and will not spend time chasing the trace through symbols they cannot see.
Quick Reference: Module Commands
# List loaded modules lsmod # Module metadata (author, license, params, dependencies) modinfo ext4 # Load/unload insmod module.ko # raw load, no dependency handling rmmod module_name # remove (fails if reference count > 0) modprobe module_name # smart load: resolves dependencies modprobe -r module_name # smart remove: also unloads orphan dependencies modprobe --show-depends foo # print the resolved load order without loading # Rebuild the dependency database depmod -a
Module Configuration
Configuration lives under /etc/modprobe.d/ (system) and /etc/modules-load.d/ (boot-time autoload). Files are read in lexical order, and later entries override earlier ones — prefix filenames with numbers (10-foo.conf, 99-overrides.conf) when ordering matters.
# Prevent a module from auto-loading (/etc/modprobe.d/blacklist.conf) blacklist pcspkr # Set module parameters (/etc/modprobe.d/options.conf) options i915 enable_fbc=1 enable_psr=1 # Load at boot (/etc/modules-load.d/modules.conf) nvidia nvidia_uvm # Alias a generic name to a specific module alias snd-card-0 snd_hda_intel
For DKMS-managed modules (out-of-tree drivers that rebuild against each new kernel), the relevant tooling is dkms status and dkms install. DKMS hooks into the package manager so that kernel upgrades automatically rebuild your local modules.
Debugging Modules
# Kernel ring buffer — your first stop for any load failure dmesg | tail -50 journalctl -k -f # follow in real time # Per-module dynamic debug (no recompile needed if CONFIG_DYNAMIC_DEBUG=y) echo 'module mymodule +p' > /sys/kernel/debug/dynamic_debug/control echo 'module mymodule -p' > /sys/kernel/debug/dynamic_debug/control # Module state and refcount cat /proc/modules # raw, with addresses lsmod # human-readable # Inspect a built module before loading modinfo ./mymodule.ko # parameters, signature, dependencies nm --defined-only ./mymodule.ko # symbols the module defines
When a module crashes the kernel, the resulting oops in dmesg includes a backtrace with symbol names. Run addr2line against the unstripped module to map mymodule+0x42 back to source line numbers.
Common Issues
| Error | Cause | Solution |
|---|---|---|
FATAL: Module not found | Module not installed | Check /lib/modules/$(uname -r)/; run depmod -a |
Required key not available | Secure Boot enforcement | Sign module with MOK key or disable Secure Boot |
Unknown symbol in module | Missing dependency or kABI mismatch | Load prerequisite modules; check modinfo |
Module in use | Reference count > 0 | Stop processes / unmount filesystems using it |
Exec format error | Built against a different kernel | Rebuild against running $(uname -r) headers |
Operation not permitted | Module loading disabled (kernel.modules_disabled=1) | Cannot re-enable without reboot |
Invalid module format | Module version magic mismatch | Rebuild; check modinfo vermagic |
Further Reading
- Linux Device Drivers, 3rd Edition - Corbet, Rubini, Kroah-Hartman (the canonical reference, free PDF)
- Linux Kernel Module Programming Guide - sysprog21, regularly updated for modern kernels
- Documentation/kbuild/modules.rst - official module build documentation
- Documentation/admin-guide/module-signing.rst - module signing and Secure Boot reference
Related concepts
Visualize the complete Linux boot sequence from BIOS/UEFI to login. Learn how GRUB, kernel, and systemd work together with interactive visualizations.
Learn FUSE (Filesystem in Userspace) for building custom filesystems. Understand how NTFS-3G, SSHFS, and cloud storage work.
Learn how initramfs enables Linux boot by loading essential drivers before the root filesystem mounts. Explore early userspace initialization.
Linux kernel architecture explained. Learn syscalls, protection rings, user vs kernel space, and what happens when you run a command.
Explore Linux memory management through interactive visualizations. Understand virtual memory, page tables, TLB, swapping, and memory allocation.
Master the Linux networking stack through interactive visualizations. Understand TCP/IP layers, sockets, iptables, routing, and network namespaces.
