Skip to main content

Linux Kernel Modules: Extending the Kernel at Runtime

Summary
Master Linux kernel modules through interactive visualizations. Learn how to load, unload, develop, and debug kernel modules that extend Linux functionality.

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.

Module vs Built-in: The Trade-off

Understanding kernel configuration choices (y/m/n)

In make menuconfig:
[*] yBuilt-in
[M] mModule
[ ] nDisabled
y
Built-in

Compiled into vmlinuz

Boot Impact

Adds ~200KB to kernel

Runtime Performance

Zero load time

Memory Usage

Always in memory (~300KB)

Flexibility

Requires kernel recompile

Best For

Root filesystem (must be available at boot)

m
Module

Separate .ko file

Boot Impact

Not loaded at boot

Runtime Performance

~5ms to load

Memory Usage

Only when mounted

Flexibility

Update without reboot

Best For

Secondary storage, external drives

The Decision Rule

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.

Most distributions compile almost everything as modules for maximum hardware compatibility.

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.

Linux Kernel Modules
What are Kernel Modules?

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.

Dynamic Loading
Load drivers and features as needed
Memory Efficient
Only load what you need
No Reboot Required
Add/remove features on the fly
Kernel Space Access
Full hardware and kernel access
Common Module Types
Filesystems
Device Drivers
Security
Network
Module Management Commands
lsmod
List loaded modules
modinfo module_name
Show module information
insmod module.ko
Insert module (low-level)
modprobe module_name
Load module with dependencies
rmmod module_name
Remove module
depmod -a
Generate module dependencies
Module Locations
/lib/modules/$(uname -r)/
# Current kernel modules
/lib/modules/$(uname -r)/kernel/
# Built-in kernel modules
/lib/modules/$(uname -r)/updates/
# Updated modules
/lib/modules/$(uname -r)/extra/
# Third-party modules

Init 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.

Module Signing with Secure Boot

How unsigned modules fail and how to sign them

Secure Boot:
1. Compile Module

Build module from source

$ make -C /lib/modules/$(uname -r)/build M=$PWD
2. Generate MOK

Create Machine Owner Key pair

$ openssl req -new -x509 -newkey rsa:2048 -keyout MOK.priv -out MOK.der
3. Sign Module

Sign with MOK private key

$ /usr/src/linux/scripts/sign-file sha256 MOK.priv MOK.der hello.ko
4. Enroll Key

Import MOK into UEFI

$ mokutil --import MOK.der
5. Reboot & Accept

Accept key in shim menu

$ # System reboot → MokManager → Enroll MOK
6. Load Module

Module loads successfully

$ insmod hello.ko
Without signing
$ insmod hello.ko
insmod: ERROR: could not insert module hello.ko:
Required key not available

Secure Boot requires all kernel modules to be signed with a trusted key. Click "Animate" to see the full signing process.

What is MOK?

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.

DKMS Auto-Signs

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:

  1. The distribution builds the kernel with a public key embedded.
  2. Every module shipped in the kernel package is signed with the matching private key.
  3. At boot, Secure Boot in firmware verifies the kernel itself is signed by a key in the platform's UEFI db.
  4. At module load, the kernel verifies the .ko against 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.

The Tainted Kernel Mystery

Understanding what kernel taint flags mean

kernel.tainted
$ cat /proc/sys/kernel/tainted
4097
Active flags:PO
Click flags to toggle
Common flags are highlighted
Active Taint Flags Explained
PProprietary

Proprietary module loaded

Example: nvidia, broadcom-wl

OOut-of-tree

Out-of-tree module loaded

Example: vboxdrv, zfs, custom modules

Why Taint Matters
  • • Kernel bug reports may be ignored
  • • Support from distro vendors limited
  • • Some features may refuse to work
  • • Debugging becomes harder
Common and Harmless
  • P - nvidia/broadcom drivers (normal)
  • O - VirtualBox, ZFS (expected)
  • E - DKMS modules (typical)
  • I - ACPI workarounds (benign)
Real-World Tip

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:

BitLetterMeaningTypical Cause
0G/PProprietary module loadedNVIDIA, VirtualBox, vendor drivers
1FModule force-loaded (insmod -f)Version mismatch override
2SKernel running on unsupported SMP configOld hardware
4MMachine Check Exception occurredCPU hardware error
9AACPI table overriddenCustom DSDT for laptops
12OOut-of-tree module loadedAnything not in mainline
13EModule with unsigned signatureLocal builds without MOK enrollment
14KKernel live-patchedKsplice, 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

ErrorCauseSolution
FATAL: Module not foundModule not installedCheck /lib/modules/$(uname -r)/; run depmod -a
Required key not availableSecure Boot enforcementSign module with MOK key or disable Secure Boot
Unknown symbol in moduleMissing dependency or kABI mismatchLoad prerequisite modules; check modinfo
Module in useReference count > 0Stop processes / unmount filesystems using it
Exec format errorBuilt against a different kernelRebuild against running $(uname -r) headers
Operation not permittedModule loading disabled (kernel.modules_disabled=1)Cannot re-enable without reboot
Invalid module formatModule version magic mismatchRebuild; check modinfo vermagic

Further Reading

If you found this explanation helpful, consider sharing it with others.

Mastodon