If you’ve only ever used desktop Linux, loadable kernel modules probably feel invisible — plug in a USB device, and the right driver just appears, loaded on demand. But walk into most embedded products, and you’ll frequently find every driver statically linked into a single monolithic kernel image, with module support disabled entirely. This lecture, part of EmbeddedPathashala’s free linux device drivers course, explains why that tradeoff exists and when it’s worth breaking from it.
insmod / modprobe
version mismatch
GPL licensing
free linux kernel development course
What You Will Learn
- What a loadable kernel module actually is, and how it differs from a built-in driver
- Why desktop distributions rely on modules heavily and embedded products often don’t
- The version-dependency trap that modules introduce between kernel and root filesystem
- The specific cases where modules remain the right choice even on embedded hardware
- How to build and load a minimal original module to see the mechanism firsthand
Prerequisites
- Familiarity with
tristateKconfig options from the first lecture in this chapter - A kernel source tree with headers matching your running kernel, for building a test module
What A Kernel Module Actually Is
A loadable kernel module is a separately compiled chunk of kernel code — typically a driver, a filesystem, or a network protocol — that is not linked into the main kernel image at build time. Instead, it is compiled to a .ko file, shipped alongside the kernel, and loaded into the running kernel’s address space on demand, either automatically when the matching hardware is detected or manually by an administrator. This is precisely the third state offered by the tristate Kconfig type covered earlier in this chapter: instead of y (built-in) or unset (excluded), m means “build it, but keep it separate.”
Why Desktop Distributions Lean On Modules Heavily
A general-purpose desktop or server distribution has no idea, at build time, what hardware it will eventually run on. It might land on a laptop with one Wi-Fi chipset, a server with an entirely different storage controller, or a virtual machine with neither. Building every conceivable driver directly into the kernel image would make that image enormous, and most of the code would never even execute on any given machine. Modules solve this cleanly: ship every driver as a module, and let the running system load only the ones its actual hardware needs.
Why Many Embedded Products Go The Opposite Way
An embedded product is the mirror image of that scenario. The hardware is fixed and known at the moment the kernel is built — the same board, the same peripherals, the same revision, every single unit off the production line. There is no unknown hardware to discover at runtime, so the primary reason modules exist on the desktop simply doesn’t apply.
Worse, modules introduce a real liability specific to embedded deployment: a version dependency between the kernel image and the root filesystem. A compiled .ko file is tied to the exact kernel version (and often the exact build configuration) it was compiled against. If a field update replaces the kernel image but not the module files in the root filesystem — or vice versa — the mismatched modules will refuse to load, and any driver that lives in one of them silently stops working. On a desktop, a failed module load might mean your webcam doesn’t work. On an embedded controller, it might mean the storage driver, the network interface, or a safety-relevant sensor doesn’t come up at all.
Root filesystem NOT updated –> modules still tagged 6.9.0-riverstone-b3Boot sequence:
kernel starts, looks for /lib/modules/6.9.0-riverstone-b4/
directory does not exist (only b3 is present)
driver load fails silently for every module-only feature
device boots “successfully” but is missing functionality
This single failure mode is the core reason it’s common for production embedded kernels to be built with module support disabled entirely, everything compiled in as y rather than m. A monolithic image is larger on disk, but it cannot suffer this particular class of field failure, because there is no separate module tree to fall out of sync.
When Modules Are Still The Right Call
None of this means modules are never appropriate on embedded targets. There are genuine, common cases where building something as m is still the better engineering decision:
| Scenario | Why A Module Helps |
|---|---|
| Proprietary or third-party drivers | Keeps GPL-licensed kernel image and a separately-licensed binary module distinct, respecting licensing boundaries |
| Reducing boot time | Deferring the load of non-essential drivers until after the critical boot path lets the device reach a usable state faster |
| Optional feature packs | A field-installable add-on feature can ship as a module without requiring a full kernel re-flash |
The licensing case deserves a closer look: the Linux kernel is GPL-licensed, and shipping a proprietary driver statically linked into the kernel image raises real licensing questions. Building that driver as a loadable module, distributed and versioned separately, is the standard way vendors keep a proprietary component out of the GPL-licensed image while still supporting the hardware.
Building A Minimal Module To See The Mechanism
Here is a small, original module — not tied to any real hardware — that demonstrates the load/unload lifecycle directly:
// ep_hello_mod.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
static int __init ep_hello_init(void)
{
pr_info("ep_hello_mod: loaded, hello from EmbeddedPathashala\n");
return 0;
}
static void __exit ep_hello_exit(void)
{
pr_info("ep_hello_mod: unloaded\n");
}
module_init(ep_hello_init);
module_exit(ep_hello_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Minimal demo module for EmbeddedPathashala");
A minimal out-of-tree Makefile to build it against your running kernel headers:
# Makefile
obj-m += ep_hello_mod.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
Build it, load it, check the log, then unload it:
$ make
$ sudo insmod ep_hello_mod.ko
$ dmesg | tail -n 2
[12345.678] ep_hello_mod: loaded, hello from EmbeddedPathashala
$ sudo rmmod ep_hello_mod
$ dmesg | tail -n 1
[12350.112] ep_hello_mod: unloaded
Notice the module only loaded because it exactly matched the running kernel’s version and build configuration — try this on a kernel it wasn’t built against, and insmod will refuse it. That single refusal is the same version-dependency mechanism responsible for the field failure mode described earlier, just triggered deliberately instead of by an incomplete update.
Real-World Use Cases
- Industrial controllers with fixed hardware — everything built-in, module support disabled, no possibility of a mismatched update
- Set-top boxes with a vendor GPU blob — the proprietary graphics driver ships as a module to respect licensing separation
- Fast-boot consumer devices — non-critical drivers deferred as modules loaded after the splash screen, to shave milliseconds off boot
Common Mistakes And Troubleshooting
- Updating the kernel image without updating
/lib/modules/— the single most common cause of “it boots but half the drivers are gone” field reports - Assuming modules always reduce image size on disk — true for the kernel image itself, but the total footprint including module files can end up similar
- Building a module against mismatched kernel headers — produces a
.kothatinsmodrejects at load time with a version signature mismatch - Leaving module auto-loading enabled without auditing which modules can be triggered by userspace — an unnecessary attack surface on a fixed-hardware device that will never need it
Best Practices
- On fixed-hardware production devices, default to built-in (
y) unless there’s a specific reason to modularize - If you do ship modules, keep the kernel image and module tree versioned and deployed together, never independently
- Reserve
tristate/module builds for licensing separation, boot-time deferral, or genuinely optional field add-ons - Disable module auto-loading on devices where the module set is fixed and known, to shrink attack surface
Performance And Security Considerations
From a performance angle, deferring non-critical drivers as modules loaded after the essential boot path is a legitimate, measurable way to shave time off boot-to-usable. From a security angle, module auto-loading is a well-known attack vector: it allows userspace, in some configurations, to trigger the kernel to load a specific module by name, expanding what an attacker with limited privileges can reach. Locking down or disabling auto-loading, and disabling module loading entirely on devices with a fixed, known driver set, is a standard embedded hardening step.
Summary And Key Takeaways
- Loadable modules let a kernel support hardware unknown at build time — valuable for desktops, less relevant for fixed-hardware embedded products
- Modules create a version dependency between the kernel image and the root filesystem that can silently break field updates
- Modules remain the right call for proprietary/licensed drivers, boot-time deferral, and optional field add-ons
- A mismatched module fails to load with a version signature error — the same protection that prevents silent corruption also causes the field failure mode when updates are incomplete
Conclusion
Whether to build a driver as a module or bake it directly into the kernel image is rarely a technical toss-up on embedded hardware — it’s a deliberate tradeoff between flexibility and the reliability risk of a version mismatch in the field. Understanding both sides lets you make that call deliberately instead of inheriting whatever a reference BSP happened to ship with. This wraps up the current run of lectures in EmbeddedPathashala’s free linux device drivers course; check the course index for what’s next.
Frequently Asked Questions
What is a loadable kernel module?
A separately compiled piece of kernel code (a .ko file) that is loaded into a running kernel on demand, instead of being linked into the main kernel image at build time.
Why do many embedded products avoid kernel modules?
Because the hardware is fixed and known at build time, modules add little value while introducing a version-dependency risk between the kernel image and the root filesystem.
What happens if the kernel image and module files fall out of sync?
Modules built for a different kernel version fail to load, silently disabling whatever driver functionality lived in them, even though the device still boots.
When should I still build a driver as a module on embedded hardware?
For proprietary/licensed drivers that need to stay separate from the GPL kernel image, to defer non-critical drivers and speed up boot, or for optional field-installable features.
Why did insmod refuse to load my module?
Most commonly a version signature mismatch – the module was built against different kernel headers than the kernel it’s being loaded into.
Is disabling module loading a security best practice?
On devices with a fixed, known driver set, yes – it removes an attack surface associated with runtime module auto-loading.
Explore The Full Course
This lecture completes this segment of EmbeddedPathashala’s free linux device drivers course. Browse the full course index for what’s covered next.
