What are Building And Cleaning Kernel Modules in Linux-Free Embedded Linux Course

Building And Cleaning Kernel Modules
A hands-on lesson from our free embedded Linux course on compiling loadable kernel modules and resetting the kernel source tree
free linux kernel development course
free linux device drivers course
free embedded linux course
kernel modules
make mrproper

If you are following along with our free linux kernel development course, you already know how to build a full kernel image and its device tree blobs. But real embedded projects rarely ship every driver built directly into the kernel. Instead, most projects compile some drivers as loadable kernel modules — separate .ko files that can be inserted or removed from a running system without a reboot. This lecture, part of our free embedded linux course, walks through exactly how the kernel build system compiles those modules, where it puts them, and how to reset your source tree back to a clean state when something goes wrong.

What You Will Learn

  • Why some kernel features are better built as modules instead of being built-in
  • How to invoke the Kbuild modules target for a cross-compiled kernel
  • Where compiled .ko files land inside the source tree, and why that is inconvenient
  • How to install modules into a staging root filesystem using INSTALL_MOD_PATH
  • The difference between clean, mrproper, and distclean, and when to use each
  • A small original demo module you can build, install, and load to see the whole pipeline work

Prerequisites

  • A configured kernel source tree with a working .config (see our earlier lectures on Kconfig and menuconfig)
  • A cross toolchain already on your PATH, for example arm-linux-gnueabihf-
  • Basic familiarity with make targets from our Kbuild lecture

Why Build Drivers As Modules

A kernel module is object code that is linked into the running kernel image at run time rather than at build time. This matters for embedded work in a few concrete ways. First, it shortens your inner development loop — you can rebuild and reload one driver in seconds instead of reflashing an entire kernel image. Second, it keeps the base kernel image small on boot media that is limited, loading optional hardware support only when it is actually plugged in. Third, it lets you distribute a driver update to devices in the field without touching the bootloader or the rest of the kernel at all.

The trade-off is that modules add a small amount of complexity: the module has to be found and loaded by user space (or auto-loaded via udev/modprobe), and you now have a second artifact — the .ko file — that has to travel alongside your kernel image and match its exact version and configuration.

Compiling Kernel Modules With Kbuild

Once you have marked a feature as m in your configuration (through menuconfig, or directly in a defconfig fragment), Kbuild already knows to treat it as a module. You do not need a separate build step for “just the modules” versus “just the built-in pieces” — a normal kernel build compiles both. But if you have already built the kernel image and only want to rebuild the modules, Kbuild exposes a dedicated target for exactly that:

$ make -j$(nproc) ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- modules

This walks the dependency graph for every symbol configured as m and compiles only those object files, linking each one into its own .ko. On a large tree this is noticeably faster than a full rebuild if you have only changed a driver or two.

Where Module Objects Land During A Build
kernel-source/
├── drivers/
│ ├── net/ethernet/realtek/r8169.ko (built where the source lives)
│ ├── mmc/host/sdhci-of-arasan.ko
│ └── gpu/drm/panel/panel-simple.ko
├── sound/
│ └── soc/codecs/wm8960.ko
└── fs/
└── nfs/nfs.ko

Notice the problem this diagram illustrates: modules are generated next to their source code, scattered across dozens of subdirectories. That layout is fine for kernel developers who know the tree, but it is not something you can hand to a root filesystem image builder as-is.

Installing Modules Into A Staging Filesystem

Kbuild solves the scattering problem with the modules_install target, which walks the tree, collects every compiled .ko, and copies them into a single directory structure keyed by kernel release string. Left alone, that target installs straight into /lib/modules on your build host — almost never what you want when cross-compiling for a separate target board. You redirect the destination with INSTALL_MOD_PATH:

$ make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- \
    INSTALL_MOD_PATH=$HOME/staging/rootfs modules_install

After this runs, every module is collected under a predictable path relative to the root you supplied:

$HOME/staging/rootfs/lib/modules/<kernel-release>/
├── kernel/drivers/net/ethernet/realtek/r8169.ko
├── kernel/drivers/mmc/host/sdhci-of-arasan.ko
├── modules.dep
├── modules.alias
└── modules.builtin

The <kernel-release> directory name comes from the kernel’s own version string (visible with make kernelrelease), and the accompanying modules.dep and modules.alias files are what let modprobe resolve dependencies and hardware aliases automatically once the board boots this filesystem.

Try It: A Minimal Original Module

To see the whole pipeline end to end, build a tiny module of your own. Save this as ep_hello.c in a scratch directory:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

static int __init ep_hello_init(void)
{
    pr_info("ep_hello: module loaded, hello from EmbeddedPathashala\n");
    return 0;
}

static void __exit ep_hello_exit(void)
{
    pr_info("ep_hello: module unloaded\n");
}

module_init(ep_hello_init);
module_exit(ep_hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeddedPathashala");
MODULE_DESCRIPTION("Minimal demo module for the modules lecture");

Pair it with a matching out-of-tree Makefile:

obj-m += ep_hello.o

all:
	$(MAKE) -C $(KDIR) M=$(PWD) modules

clean:
	$(MAKE) -C $(KDIR) M=$(PWD) clean

Build it against your kernel tree and load it:

$ make KDIR=/path/to/kernel-source
$ sudo insmod ep_hello.ko
$ dmesg | tail -n 2
[  132.114201] ep_hello: module loaded, hello from EmbeddedPathashala
$ sudo rmmod ep_hello
$ dmesg | tail -n 1
[  140.552017] ep_hello: module unloaded

Cleaning The Kernel Source Tree

After enough build iterations, a kernel tree accumulates object files, generated headers, and stray artifacts that can make a fresh build behave unpredictably. Kbuild gives you three progressively more aggressive cleaning targets.

Target What It Removes When To Use It
make clean Object files and most intermediate build products Between quick rebuilds when you just want a fresh compile
make mrproper Everything clean removes, plus your .config Returning the tree to exactly how it looked right after checkout, before reconfiguring from scratch
make distclean Everything mrproper removes, plus editor backup files and patch leftovers Preparing a tree for archiving, sharing, or a completely fresh clone-equivalent state

A useful habit: run mrproper whenever a build fails in a confusing way after switching branches or applying a patch, then reconfigure from your saved defconfig. It removes an entire class of “stale generated header” bugs in one step.

Common Mistakes And Troubleshooting

  • Forgetting ARCH/CROSS_COMPILE on modules_install: the install step still needs to know the target architecture, even though it is mostly copying files, because it strips and signs modules using the cross toolchain.
  • Installing straight to /lib/modules on the host: always set INSTALL_MOD_PATH when cross-compiling, or you will pollute your development machine with a foreign-architecture module tree.
  • Module refuses to load with “invalid module format”: the module’s kernel-release string does not match the running kernel’s uname -r exactly. Rebuild against the exact tree and config that produced the running image.
  • Running mrproper without saving your .config first: copy your working config out with cp .config myboard_defconfig_backup before a destructive clean.

Best Practices

  • Keep a saved defconfig per board in version control so an mrproper is never destructive to your actual work.
  • Use the dedicated modules target for incremental driver work instead of a full image rebuild — it is dramatically faster.
  • Always pair a cross-compiled modules_install with the matching INSTALL_MOD_PATH, and copy that staging tree into your root filesystem image as a distinct build step.
  • Version your module tree alongside the kernel image you shipped it with, since a mismatch is a common field-support headache.

Summary And Key Takeaways

  • The modules target compiles everything configured as m, producing .ko files scattered throughout the source tree.
  • modules_install collects those .ko files into a version-keyed directory, and INSTALL_MOD_PATH redirects that collection into a staging root filesystem instead of the build host.
  • clean, mrproper, and distclean offer three levels of tree cleanup, with mrproper being the one most embedded developers reach for when reconfiguring from scratch.

Conclusion

Modules give embedded Linux projects real flexibility: smaller boot images, faster driver iteration, and field-updatable hardware support. The mechanics are simple once you see the pipeline as a whole — compile with the modules target, collect with modules_install and INSTALL_MOD_PATH, and reset with mrproper whenever the tree gets into a confusing state. In the next lecture in this free linux device drivers course, we move from building to actually booting a kernel image on real hardware and inside QEMU.

Frequently Asked Questions

Why does insmod fail with “invalid module format”?

This almost always means the module’s embedded kernel version string does not match the running kernel’s uname -r. Rebuild the module against the exact source tree and configuration used to build the currently running kernel image.

What is the difference between insmod and modprobe?

insmod loads exactly the module file you point it at and does nothing about dependencies. modprobe consults modules.dep, generated during modules_install, and loads any dependency modules automatically before loading the one you asked for.

Do I need INSTALL_MOD_PATH if I am building natively on the target board?

No. If you are building directly on the target hardware, the default install location of /lib/modules is already correct, since the build host and the deployment target are the same machine.

When should I use mrproper instead of clean?

Use clean for routine rebuilds. Reach for mrproper when you suspect stale generated headers or a corrupted .config are causing confusing build failures, since it wipes the configuration along with all intermediates.

Can I build only one module instead of every configured module?

Yes. From an out-of-tree Makefile like the one in this lecture, or by pointing make M=drivers/path/to/driver modules at the specific driver’s directory from within the kernel tree.

What does distclean remove that mrproper does not?

Editor backup files (like *~ or *.orig) and leftover patch reject files, on top of everything mrproper already removes.

Why is my module missing from the staging rootfs after modules_install?

Check that INSTALL_MOD_PATH was actually set on the modules_install invocation — omitting it silently installs to the build host’s /lib/modules instead of your intended staging directory.

Continue Building Your Embedded Linux Skills

This lecture is part of EmbeddedPathashala’s free linux kernel development course, built for students who want real depth, not shortcuts.

Leave a Reply

Your email address will not be published. Required fields are marked *