Linux kernel modules are how you extend the kernel’s functionality without recompiling the whole thing. The surprising thing is that they’re not just for hardware drivers; you can load modules for entirely software-based features like network protocols, filesystem types, and even custom security policies.
Let’s see a module in action. Imagine you need to enable the tun module, which allows for creating virtual network interfaces (like those used by VPNs).
# Check if tun is already loaded
lsmod | grep tun
# If not loaded, load it
sudo modprobe tun
# Verify it's loaded
lsmod | grep tun
The output of lsmod | grep tun would change from nothing to something like:
tun 20480 0
This shows the tun module is now part of the running kernel. To unload it (if no interfaces are using it):
sudo rmmod tun
Now, let’s build a mental model. The Linux kernel is the core of the operating system, managing hardware, processes, and memory. Kernel modules are pieces of code that can be dynamically inserted into or removed from the kernel while it’s running. This is like hot-swapping components in a running computer, but for software.
The primary problem modules solve is flexibility and efficiency. Instead of having every possible driver and feature compiled directly into the kernel image (making it huge and slow to load), only the necessary modules are loaded on demand. This means a minimal kernel can support a vast range of hardware and software configurations.
Internally, when you run modprobe <module_name>, the modprobe utility looks for the module file (typically in /lib/modules/$(uname -r)/) and its dependencies. It then uses system calls to ask the kernel to load the module’s code into memory, resolve any symbols (functions or variables) it needs from other loaded modules or the core kernel, and initialize it. rmmod does the reverse, though it will fail if the module is in use.
The exact levers you control are the module parameters. Many modules accept configuration options at load time. For example, to load the i2c-dev module and allow user-space access to I2C devices, you might do:
sudo modprobe i2c-dev enable_i2cdev=1
You can see available parameters for a module using modinfo <module_name>.
modinfo i2c-dev
This might show parm: enable_i2cdev:Enable user-space access to I2C devices (int) among other parameters.
Troubleshooting module loading often involves checking dependencies, permissions, and the kernel’s log. If modprobe mymodule fails, the first place to look is dmesg.
dmesg | tail
This will often show errors like "mymodule: disagrees about version of symbol xyz" or "mymodule: Unknown symbol xyz".
One common pitfall is module versioning. When the kernel is updated, existing modules might become incompatible because kernel internal structures or function signatures have changed. The kernel embeds a version string into each module, and it checks this against its own internal version string when loading. If they don’t match, you get a versioning error, and the module won’t load. This is why it’s crucial to rebuild or obtain modules specifically compiled for your exact kernel version (uname -r).
Another frequent issue is missing dependencies. If mymodule requires dependent_module, and dependent_module isn’t loaded or available, modprobe mymodule will fail with an error like "ERROR: Module dependent_module not found." You’d then need to load dependent_module first, or ensure it’s installed.
Permissions can also be a blocker. Loading modules requires root privileges. If a user-space application tries to load a module (which is generally not recommended or possible without specific interfaces), it will fail.
Finally, some modules might require specific firmware to be present in /lib/firmware/. If a module fails to load and dmesg mentions "failed to load firmware" or similar, you’ll need to install the appropriate firmware package for your hardware.
The next logical step after mastering module loading and unloading is understanding how to create your own kernel modules, or how to configure module loading behavior automatically on boot.