Kernel Modules
Topic 74

Kernel Modules

KernelDrivers

The Linux kernel is modular: most drivers and many optional features are not compiled into the kernel image at all. They ship as separate .ko files under /lib/modules/$(uname -r)/ and load into the running kernel on demand. A kernel module runs in ring 0 with full hardware access — it is kernel code, not a user-space program — yet it can be inserted and removed at runtime without rebooting. This is how the same generic kernel that Ubuntu installs boots on a laptop, a cloud VM, and a storage server: it carries thousands of modules and loads only the handful each machine actually needs.

The operational consequence is that hardware support and driver behavior are something you manage, not something fixed at build time. You add support for a new NIC by loading its module, tune a driver by passing it parameters, and stop a buggy or unwanted driver from ever loading by blacklisting it. The catch is that a module runs with kernel privileges, so a bad one panics the whole machine — and under Secure Boot an unsigned module is refused outright. The tools are lsmod, modinfo, and modprobe, plus the configuration directories under /etc/modprobe.d and /etc/modules-load.d.

Modules versus Built-in

A feature in the kernel is either built in — compiled directly into the vmlinuz image and always present — or built as a module that loads later. Distributions compile the small set of things needed to mount the root filesystem and bring the CPU up (the core scheduler, base memory management, the filesystem the root partition uses) directly into the image, and ship everything else — most filesystems, every network and storage driver, sound, USB, virtualization helpers — as modules. The reason is size and flexibility: a kernel with every driver compiled in would be enormous and would carry code for hardware no single machine has.

You can tell which is which. The kernel build config records the choice as =y (built in) or =m (module) in /boot/config-$(uname -r), and only modules ever appear in lsmod. This distinction matters when a feature refuses to behave as a runtime option: a parameter for a built-in driver cannot be changed with modprobe at all, only on the kernel command line at boot.

Managing Modules

lsmod lists what is currently loaded, with each module's memory size and its use count — how many other modules or open references depend on it. A module with a non-zero use count cannot be removed until its dependents are gone. modinfo <name> reads metadata straight out of the .ko file: the author, the description, the parameters it accepts, its dependencies, and whether it is signed. Reach for modinfo before loading an unfamiliar module to see what options it takes.

modprobe is the command to load and unload. The reason it exists rather than the lower-level insmod is dependency resolution: modprobe reads modules.dep (generated by depmod) and pulls in every prerequisite module in the right order, then loads the one you asked for. insmod takes a single .ko path and loads exactly that file — if it needs another module first, it simply fails with Unknown symbol. Removal is modprobe -r <name>, which also unloads now-unused dependencies; rmmod is its blunt equivalent.

# inspect, then load with dependencies resolved
modinfo e1000e
sudo modprobe e1000e
lsmod | grep e1000e
# unload it and any deps it pulled in
sudo modprobe -r e1000e

On Red Hat and derivatives the commands are identical — kmod provides modprobe, lsmod, and modinfo on every modern distribution. What differs is only where vendor and third-party modules come from: Debian and Ubuntu use DKMS packages and linux-modules-extra, while RHEL ships extra drivers in kernel-modules-extra and signs out-of-tree modules through a different key-enrollment flow.

Loading at Boot and Blacklisting

Most modules load automatically: udev sees a device appear, looks up the module that claims its hardware ID, and loads it. When you need a module that has no triggering device — a tunable filesystem helper, a watchdog, an overlay driver for containers — list its name in a file under /etc/modules-load.d/ and systemd-modules-load.service loads it at every boot. One module name per line; the filename ends in .conf.

Blacklisting is the inverse, and the source of a recurring mistake. Putting blacklist <name> in a file under /etc/modprobe.d/ stops the module from being loaded automatically by udev or as a dependency — but it does not stop an explicit modprobe <name>, and it does not help if the module is already baked into the initramfs. To truly prevent loading, combine blacklist with an install <name> /bin/false line, then rebuild the initramfs so the early-boot environment carries the change too.

# /etc/modprobe.d/blacklist-nouveau.conf
blacklist nouveau
install nouveau /bin/false
# then rebuild the initramfs so early boot honors it
sudo update-initramfs -u        # Debian/Ubuntu
sudo dracut -f                   # RHEL/Fedora

Module Parameters

Drivers accept parameters that change their behavior. modinfo -p <name> lists them; /sys/module/<name>/parameters/ shows the live values of a loaded module, some of which are writable on the fly. For a one-off you pass options on the load line — modprobe usbcore autosuspend=-1 — but that lasts only until the next reboot or reload.

For a persistent parameter, write an options line into /etc/modprobe.d/ so it applies every time the module loads. As with blacklisting, if the module loads from the initramfs (a storage or filesystem driver, for example) the option only takes effect after an initramfs rebuild — the early-boot copy of the config is a snapshot, not a live read of /etc.

# /etc/modprobe.d/i915.conf — persistent driver option
options i915 enable_guc=2

Signing and Secure Boot

With UEFI Secure Boot enabled, the kernel enforces a signature check on every module it loads. In-tree modules shipped by the distribution are signed with a key the firmware trusts, so they load normally. An out-of-tree module — an NVIDIA driver, a VirtualBox helper, anything built locally via DKMS — is unsigned by default and is refused with Required key not available in dmesg. The load silently fails and the hardware it drives simply does not work, which is a confusing symptom if you do not know to look for it.

The fix is to sign the module with a Machine Owner Key and enroll that key in the firmware. On Debian and Ubuntu, DKMS can generate a MOK and call mokutil --import for you; you confirm the enrollment in a blue MOK Manager screen on the next reboot, after which your locally built modules load. The alternative — disabling Secure Boot — works but trades away the boot-chain integrity that Secure Boot exists to provide.

Common Mistakes
  • Loading with insmod /path/to/foo.ko instead of modprobe fooinsmod does no dependency resolution, so the load fails with Unknown symbol in module the moment the driver needs a prerequisite that is not already loaded.
  • Blacklisting only with a blacklist line and expecting it to be absolute — it stops automatic loading but not an explicit modprobe or a dependency pull-in. Without the matching install <name> /bin/false the module still loads when something asks for it by name.
  • Editing /etc/modprobe.d/ for a storage or filesystem driver and never rebuilding the initramfs — the early-boot environment keeps the old config, so the blacklist or option is ignored until update-initramfs -u (or dracut -f) bakes the change in.
  • Trying to change a parameter on a built-in driver with a modprobe options line — a =y feature is never loaded as a module, so the option is silently inert; built-in parameters must go on the kernel command line in the bootloader instead.
  • Building an out-of-tree module on a Secure Boot machine and not signing it — the kernel refuses it with Required key not available, the load fails silently, and the device it drives appears simply broken.
  • Forcing out a module that is still in use with rmmod -f — yanking a driver with a non-zero use count can hang or panic the kernel; modprobe -r respects dependencies and refuses when the module is busy.
Best Practices
  • Use modprobe for every load and unload — it resolves dependencies through modules.dep and pulls in prerequisites in order. Keep insmod and rmmod only for testing a single freshly compiled .ko.
  • Run modinfo <name> before loading an unfamiliar module to read its parameters, dependencies, and signature status straight from the file.
  • Blacklist with both blacklist <name> and install <name> /bin/false in a named file under /etc/modprobe.d/, then rebuild the initramfs so early boot honors it.
  • Persist module parameters as options <name> key=value in /etc/modprobe.d/ rather than on the load command line, so the setting survives reboots and reloads.
  • Rebuild the initramfs with update-initramfs -u on Debian/Ubuntu or dracut -f on RHEL after any change touching a module that loads during early boot.
  • On Secure Boot machines, sign locally built modules with an enrolled Machine Owner Key — let DKMS generate and mokutil --import the MOK rather than disabling Secure Boot.
  • List boot-time modules that have no triggering device in /etc/modules-load.d/*.conf, one name per line, instead of scripting modprobe calls in a startup script.
Comparable toolsWindows — kernel-mode device drivers loaded by Plug and Play and the Service Control Manager; signature enforcement is mandatory on 64-bit WindowsmacOS — legacy KEXTs, now largely replaced by user-space System Extensions and DriverKit; KEXTs require notarization and a rebootBSDkldload, kldunload, and kldstat manage loadable kernel modules on FreeBSD

Knowledge Check

Why is modprobe foo preferred over insmod /lib/modules/.../foo.ko?

  • modprobe reads modules.dep and loads every prerequisite module in order, while insmod loads one file and fails with Unknown symbol if a dependency is missing
  • insmod can only load features already compiled into the kernel image, whereas modprobe is the sole command that knows how to locate and handle loadable .ko module files
  • modprobe loads each module in user space for memory safety, while insmod inserts the very same code straight into ring 0
  • insmod strictly requires a valid Secure Boot signature, while modprobe quietly bypasses the kernel signature check entirely

You add blacklist nouveau to /etc/modprobe.d/ but the driver still loads at boot. What is the most likely reason?

  • The module is baked into the initramfs, so the early-boot environment never sees the new config — you must rebuild it with update-initramfs -u and add an install ... /bin/false line
  • A blacklist line only ever takes effect once the module's in-kernel use count has dropped all the way to zero, which the GPU keeps above zero
  • Blacklisting directives have to live in /etc/modules-load.d/ instead of /etc/modprobe.d/, so the entry is being silently ignored at boot
  • The driver is compiled directly into the kernel image as a static built-in rather than a loadable module, so no configuration directory anywhere under /etc can affect whether it loads at boot

Under UEFI Secure Boot, a locally built DKMS module fails with Required key not available in dmesg. What is happening?

  • The module is not signed by a trusted key, so the kernel refuses to load it; signing it with an enrolled Machine Owner Key resolves it
  • The kernel headers used to compile the module were from the wrong release, so its exported symbol table mismatches the version of the running kernel
  • The module exceeded the strict maximum binary size that Secure Boot permits for any out-of-tree driver loaded at runtime
  • DKMS-built modules simply can never load on a system with Secure Boot active, regardless of how they are signed

What is the correct way to make a module parameter persist across reboots?

  • Write an options <name> key=value line in a file under /etc/modprobe.d/ so it applies every time the module loads
  • Pass it just once on a modprobe command line; the kernel caches the value under /sys/module/ and replays it automatically at every boot
  • Echo the value straight into /sys/module/<name>/parameters/<key>, which the kernel then flushes back to disk automatically
  • Add the module name to a file in /etc/modules-load.d/ with the parameter appended after a space on the same line

You got correct