Previous Lecture
Next Lecture
menuconfig M option
/lib/modules directory
lsmod command
modinfo command
Built-in vs Loadable
Module dependencies
Where Do All Those .ko Files Come From?
When you install Linux on a machine, thousands of kernel modules are already present on your system — long before you ever write a single line of driver code. These modules handle everything from USB keyboards to Wi-Fi adapters to GPU memory management. They are maintained as part of the official Linux kernel source tree itself.
In this tutorial, we explore how these in-tree modules differ from the out-of-tree modules you write yourself, how the kernel build system handles them, and the practical tools you use to inspect and manage modules on a live system.
Three Ways a Feature Can Exist in the Kernel
When you configure a Linux kernel using make menuconfig, every configurable feature presents you with three choices. Understanding this tristate is fundamental to kernel module development.
| Symbol | In .config | What It Means | Result |
|---|---|---|---|
| [*] | CONFIG_XXX=y | Feature compiled into the kernel | Always available, baked into vmlinuz. No .ko file. |
| [M] | CONFIG_XXX=m | Feature compiled as a loadable module | Creates a .ko file. Loaded on demand. |
| [ ] | # CONFIG_XXX is not set | Feature disabled entirely | Neither built-in nor module. Not available. |
This choice happens when you run make menuconfig (or make xconfig) before building the kernel. The result is saved in a file called .config in the kernel source root. The kernel build system reads this file and decides what to compile into the image and what to build as a .ko.
# Example lines from a .config file
CONFIG_USB_SERIAL=m # USB serial driver → built as module
CONFIG_EXT4_FS=y # ext4 filesystem → built into kernel
# CONFIG_NFS_V4 is not set # NFSv4 → disabled entirely
Where Do In-Tree Modules Live After Building?
After you build a kernel and run make modules_install, all the .ko files get placed in a directory structure under /lib/modules/. Each kernel version gets its own subdirectory named after the version string returned by uname -r.
# See all kernel versions that have modules installed
$ ls /lib/modules/
6.8.0-45-generic 6.8.0-47-generic
# Explore the directory structure for the running kernel
$ ls /lib/modules/$(uname -r)/
build kernel modules.alias modules.builtin
modules.dep modules.devname modules.order modules.symbols source
# The actual .ko files are organized by subsystem inside the 'kernel' directory
$ ls /lib/modules/$(uname -r)/kernel/
arch block crypto drivers fs lib mm net sound
# Count all modules for the running kernel
$ find /lib/modules/$(uname -r)/ -name "*.ko*" | wc -l
On a modern Ubuntu 24.04 or Fedora 40 system with kernel 6.8 or 6.9, you will typically find between 5,000 and 7,000 kernel modules. This large count makes sense — Linux supports an enormous range of hardware across servers, desktops, embedded boards, and everything in between. Distributors configure the kernel to support as many devices as possible, but compile uncommon drivers as modules so they do not bloat the base image.
The modules.dep File — Tracking Dependencies
Modules often depend on other modules. For example, a USB Wi-Fi driver might depend on the USB core module and the wireless stack module. The kernel uses a dependency file called modules.dep to track these relationships. modprobe reads this file to know what to load first.
# modules.dep shows each module and what it depends on
# Format: module_path: dependency1_path dependency2_path ...
$ cat /lib/modules/$(uname -r)/modules.dep | grep "iwlwifi" | head -3
# After building your own kernel, always run this to regenerate the dep file:
$ sudo depmod -a
Practical Tool: lsmod — See What Is Currently Loaded
lsmod shows you all kernel modules that are currently loaded in memory. It reads from the kernel’s internal module list and formats it nicely.
$ lsmod
Module Size Used by
nvidia_uvm 1413120 0
nvidia_drm 98304 4
nvidia 56893440 282 nvidia_uvm,nvidia_drm
drm_kms_helper 262144 1 nvidia_drm
drm 671744 6 drm_kms_helper,nvidia_drm
e1000e 335872 0
usbhid 73728 0
hid 151552 1 usbhid
Let’s understand the three columns:
| Column | What It Shows | Example |
|---|---|---|
| Module | Name of the loaded module | e1000e |
| Size | How many bytes of kernel memory this module occupies | 335872 (≈ 328 KB) |
| Used by | Reference count + which other modules depend on this one | nvidia_uvm,nvidia_drm (means nvidia module is needed by both) |
The Used by column is critical. A module with a non-zero use count cannot be removed with rmmod. You must first unload the modules that depend on it. For example, you cannot remove the nvidia module while nvidia_drm and nvidia_uvm are still loaded.
Practical Tool: modinfo — Inspect a Module’s Metadata
Every properly written kernel module embeds metadata inside itself — things like its author, a description, what license it is under, and what parameters it accepts. The modinfo command extracts and displays this metadata without actually loading the module.
# Get information about the e1000e Intel Ethernet driver module
$ modinfo e1000e
filename: /lib/modules/6.8.0-45-generic/kernel/drivers/net/ethernet/intel/e1000e/e1000e.ko.zst
version: 3.2.6-k
license: GPL v2
description: Intel(R) PRO/1000 Network Driver
author: Intel Corporation <linux.nics@intel.com>
srcversion: 4E4F0B85F33B53D4C4C7CE7
alias: pci:v00008086d...
depends: ptp
retpoline: Y
intree: Y
name: e1000e
vermagic: 6.8.0-45-generic SMP preempt mod_unload modversions
Notice the vermagic field. This is very important — it tells you exactly which kernel version and configuration this module was compiled for. If you try to load a module compiled for kernel 6.7 into a 6.8 kernel, the kernel will refuse it unless version checking is disabled. We will cover this in detail when we discuss version magic.
sudo insmod mymod.ko debug=1 timeout=5000In-Tree vs Out-of-Tree Modules: What Is the Difference?
| Aspect | In-Tree Module | Out-of-Tree Module |
|---|---|---|
| Location | Inside the Linux kernel source tree (under drivers/, fs/, net/, etc.) |
Separate directory, outside the kernel source. Your working directory. |
| Built by | make modules during a full kernel build |
make in your module directory, referencing the kernel headers |
| Installed to | /lib/modules/$(uname -r)/kernel/ |
Wherever you place it — or copied there manually |
| modinfo intree | Y |
N (kernel marks itself as “tainted”) |
| Kernel taint | No taint on loading | Kernel taint flag O (Out-of-tree) gets set when loaded |
| Example | e1000e Intel Ethernet driver, ext4 filesystem, USB HID driver | Your custom device driver, a hello world module, an NVIDIA proprietary driver |
Kernel Taint: What It Means and Why It Matters
When you load an out-of-tree module (or a module without a GPL-compatible license), the kernel sets a taint flag. This is the kernel’s way of saying “the state of this kernel is no longer pure — something unofficial has been loaded into it.”
Taint flags are visible in the kernel log (dmesg) and in /proc/sys/kernel/tainted. Each bit represents a different reason for taint. Some common ones:
| Flag Letter | Meaning |
|---|---|
| P | A proprietary (non-GPL) module was loaded |
| O | An out-of-tree module was loaded |
| E | An unsigned module was loaded (on kernels with module signing enforcement) |
| W | A warning was issued during kernel operation |
Why does this matter practically? When you report a kernel bug to a distro or to the kernel community, the first thing they check is whether the kernel is tainted. A tainted kernel means they cannot rule out that the unofficial module caused the bug. This is not a security concern per se — it is a quality-assurance mechanism. During your development work, you will routinely see taint flag O and that is perfectly normal.
# Check current taint status of your kernel
$ cat /proc/sys/kernel/tainted
0 # 0 = clean, untainted
# After loading your out-of-tree module:
$ sudo insmod ./mymodule.ko
$ cat /proc/sys/kernel/tainted
4096 # bit 12 set = out-of-tree module loaded
Module Signing in Kernel 6.x — A Security Feature
Modern Linux kernels (especially in kernel 6.x) support module signing. This means the kernel can require that every module be cryptographically signed before loading it. This is designed to prevent someone from loading a malicious kernel module (for example, a rootkit) on a secured system.
Private Key
(with embedded signature)
For your development work, module signing enforcement is typically disabled. But if you are writing drivers for a production embedded product running on a kernel with Secure Boot enabled, you will need to sign your modules. Kernel 6.x makes this easier with improved tooling in the scripts/ directory of the kernel source.
Essential Module Management Commands — Quick Reference
| Command | What It Does | Handles Dependencies? |
|---|---|---|
| insmod module.ko | Load a specific .ko file directly | No — manual |
| rmmod module_name | Unload a module by name | No |
| modprobe module_name | Load module + all dependencies automatically | Yes — automatic |
| modprobe -r module_name | Unload module + unused dependencies | Yes — automatic |
| lsmod | List all currently loaded modules | N/A |
| modinfo module_name | Show metadata of a module (without loading it) | N/A |
| depmod -a | Rebuild the modules.dep dependency file | N/A |
| dmesg | tail -20 | See kernel ring buffer — check module load messages | N/A |
Built-In Features vs Loadable Modules: A Practical Example
How do you know which features are compiled into the kernel versus loaded as modules? Two files in /lib/modules/$(uname -r)/ help you:
# modules.builtin lists features compiled directly into the kernel binary
$ cat /lib/modules/$(uname -r)/modules.builtin | grep "ext4"
kernel/fs/ext4/ext4.ko # if ext4 is built-in, it appears here
# modules.builtin.modinfo gives parameter info for built-in features
$ cat /lib/modules/$(uname -r)/modules.builtin.modinfo | head -20
Memory Mgmt (=y)
ext4 FS (=y)
VFS (=y)
Interrupt handling (=y)
🎓 Interview Questions & Answers
When a kernel configuration option is set to =m, the corresponding feature is compiled as a loadable kernel module (a .ko file) rather than being built into the kernel image. This means the feature is not available immediately at boot but can be loaded on demand using insmod or modprobe.
=y means the feature is compiled into the kernel image itself. It is always available from the moment the kernel boots and consumes memory permanently. =m means the feature is compiled as a separate .ko file and only loaded into memory when needed — saving RAM when the feature is not in use.
lsmod displays all kernel modules currently loaded in memory. It reads from /proc/modules. The three columns are: Module (name of the module), Size (bytes of kernel memory it occupies), and Used by (reference count plus names of other modules that depend on this one). A non-zero use count prevents the module from being unloaded.
The vermagic field contains the kernel version string and build configuration flags (such as SMP, preemption model) for which the module was compiled. When you try to load a module, the kernel compares the module’s vermagic with its own version. If they do not match, the kernel refuses to load the module to prevent incompatible code from running. This check can be disabled with insmod --force but doing so is dangerous.
A tainted kernel is one where something outside the mainline, supported kernel code has been loaded or an unusual condition has occurred. Common reasons include: loading an out-of-tree module (flag O), loading a non-GPL module (flag P), loading an unsigned module (flag E), or a kernel warning being triggered (flag W). The taint status is stored in /proc/sys/kernel/tainted as a bitmask. Kernel developers use this to determine whether third-party code might have contributed to a reported bug.
A non-zero “Used by” count means other loaded modules are depending on this module — they may be using functions or symbols it exports. If you forcibly removed the module while others depend on it, those dependent modules would have dangling pointers to code that no longer exists in memory, which would cause a kernel crash. The kernel prevents this by refusing rmmod on in-use modules. You must first unload all dependent modules.
You run sudo depmod -a. This command scans all installed .ko files, analyzes their symbols and dependencies, and regenerates the modules.dep, modules.alias, and modules.symbols files in /lib/modules/$(uname -r)/. Without this step, modprobe will not be able to find or auto-load your newly installed module.
The Linux kernel itself is licensed under GPL v2. Many internal kernel functions and symbols are exported with EXPORT_SYMBOL_GPL, which means they are only accessible to modules that declare a GPL-compatible license. If your module declares a proprietary license (for example, “Proprietary”), it cannot use these GPL-only symbols, which significantly limits what it can do. Declaring GPL in your module metadata with MODULE_LICENSE("GPL v2") is therefore important not just legally but technically.
Ready to Write Your First Kernel Module?
Next tutorial: We write a Hello World kernel module from scratch, understand the Makefile structure, and run it live on a kernel 6.x system.
Previous Lecture
Next Lecture
