Beginner–Intermediate
Linux 6.x
1 of 2
Before you begin
When you write a Linux kernel module and load it, the kernel keeps a record of it in memory. That record contains the module’s name, how much kernel memory it occupies, whether any other module depends on it, and its current state (loading, live, or going away). Linux gives you several tools to read that record — from the simple lsmod command all the way down to the raw virtual files in /proc/modules and /sys/module/.
This tutorial explains all of them from first principles, using Linux kernel 6.x as the reference. No assumptions are made about prior kernel internals knowledge.
1. One binary, many names: kmod
You probably know commands like lsmod, insmod, rmmod, modinfo, modprobe, and depmod. Here is something surprising: every single one of them is the same program.
They are all symbolic links pointing to a single binary called kmod. When you run any of them, kmod reads its own invocation name (argv[0]) and behaves accordingly.
$ ls -la /usr/bin/lsmod /usr/bin/rmmod /usr/bin/modinfo /sbin/modprobe /sbin/depmod
lrwxrwxrwx 1 root root 4 /usr/bin/lsmod -> kmod
lrwxrwxrwx 1 root root 4 /usr/bin/rmmod -> kmod
lrwxrwxrwx 1 root root 4 /usr/bin/modinfo -> kmod
lrwxrwxrwx 1 root root 4 /sbin/modprobe -> ../bin/kmod
lrwxrwxrwx 1 root root 4 /sbin/depmod -> ../bin/kmod
The kmod project replaced the older module-init-tools package (which reached end-of-life in 2011). kmod is built on top of a library called libkmod — that library does the actual heavy lifting of reading dependency files, applying module options, and calling kernel system calls.
2. lsmod — reading the module list
lsmod is the simplest inspection tool. It takes no arguments, reads /proc/modules, reformats it into a neat table with a header row, and prints it. That is literally everything it does.
$ lsmod
Module Size Used by
bluetooth 983040 38 btrtl,btusb,btbcm,btintel,rfcomm,bnep
cfg80211 1138688 3 iwlwifi,mac80211,iwlmvm
e1000e 299008 0
drm 618496 6 drm_kms_helper,i915
The output has three columns. Here is what each one means:
| Column | What it shows | Details |
|---|---|---|
| Module | The module’s name | Usually the filename of the .ko file without any extension. A custom name can be embedded at build time using MODULE_ALIAS(). |
| Size | Kernel memory used (bytes) | How much kernel RAM this module occupies right now. This is not the size of the .ko file on disk — it is the in-memory footprint after loading and relocation. |
| Used by | Reference count + dependents | The number shows how many things are actively using this module. The comma-separated names are other loaded modules that depend on this one. A count of 0 usually means nothing is using it and it can be unloaded. |
The count can be nonzero because of kernel-internal users (for example, an open file handle, an active network connection, or a mounted filesystem) that are not themselves modules. Those internal users are not listed by name, but they still increment the reference count.
3. /proc/modules — the raw data behind lsmod
/proc/modules is a virtual file that the kernel generates fresh every time you read it. lsmod is just a pretty-printer on top of this file. Reading it directly gives you two extra fields that lsmod hides: the module’s load state and its kernel memory address.
$ cat /proc/modules
bluetooth 983040 38 btrtl,btusb,btbcm,btintel,rfcomm,bnep, Live 0xffffffffc0b40000
cfg80211 1138688 3 iwlwifi,mac80211,iwlmvm, Live 0xffffffffc0800000
e1000e 299008 0 - Live 0xffffffffc06c0000
Each line has six space-separated fields:
The three load states
Field 5 can be one of three values. These map directly to internal kernel states:
4. /sys/module/ — the per-module sysfs directory
Every loaded module gets its own directory under /sys/module/. Unlike /proc/modules which is one big file, sysfs gives you individual attributes as separate readable (and sometimes writable) files.
$ ls /sys/module/bluetooth/
coresize holders initsize initstate notes parameters refcnt sections srcversion taint uevent
| File / Directory | What it contains |
|---|---|
| initstate | Text form of the module state: live, coming, or going |
| refcnt | Current reference count (same as the third column in /proc/modules) |
| coresize | Size of the module’s core memory region in bytes (text + data + rodata) |
| initsize | Size of the init-only memory region (freed after module_init() returns) |
| srcversion | A hash of the source code used to build this module, matches modinfo srcversion |
| taint | Taint flags this module added to the kernel (e.g. unsigned, out-of-tree) |
| holders/ | Symlinks to modules that depend on this one (the reverse of the “Used by” list) |
| parameters/ | One file per module parameter — some are writable at runtime! |
| sections/ | Kernel virtual addresses of ELF sections (useful for debuggers / oops decoding) |
Changing a module parameter at runtime
If a module exposes a writable parameter via module_param(), you can change its value without reloading the module:
# Read current value of a parameter
$ cat /sys/module/usbcore/parameters/autosuspend
# Change it (requires root)
$ echo 2 | sudo tee /sys/module/usbcore/parameters/autosuspend
5. modinfo — reading module metadata
modinfo reads metadata that was embedded by the compiler directly into the .ko file at build time. You can run it on a module that is not even loaded — you just need the .ko file or the module name (it searches /lib/modules/$(uname -r)/).
$ modinfo bluetooth
filename: /lib/modules/6.8.0-45-generic/kernel/net/bluetooth/bluetooth.ko.xz
version: 2.22
license: GPL
description: Bluetooth Core ver 2.22
author: Marcel Holtmann <marcel@holtmann.org>
srcversion: AC3C4F82E1E52F85E07AABB
alias: net-pf-31
depends:
retpoline: Y
intree: Y
name: bluetooth
vermagic: 6.8.0-45-generic SMP preempt mod_unload modversions
sig_id: PKCS#7
signer: Build time autogenerated kernel key
sig_hashalgo: sha512
What every field means
| Field | Meaning | Kernel macro that sets it |
|---|---|---|
| filename | Full path to the .ko file on disk. Note: modern distros compress modules as .ko.xz or .ko.zst | — |
| license | The module’s license declaration. Must be a GPL-compatible string to access GPL-only kernel symbols | MODULE_LICENSE() |
| description | A short human-readable description of what the module does | MODULE_DESCRIPTION() |
| author | Module author contact | MODULE_AUTHOR() |
| version | Module version string | MODULE_VERSION() |
| srcversion | A hash computed from the source files at build time. Useful for checking if a loaded module matches a .ko on disk | auto-generated |
| alias | Device ID patterns used for automatic loading (e.g. PCI IDs, USB IDs) | MODULE_ALIAS() |
| depends | Comma-separated list of other modules this module requires at load time (hard dependencies) | auto-derived from symbol imports |
| retpoline | Y if the module was compiled with the Spectre v2 retpoline mitigation | compiler flag |
| intree | Y if built from the official kernel source tree. Out-of-tree modules omit this and taint the kernel | MODULE_INFO(intree, “Y”) |
| vermagic | The kernel version + key config flags that must match the running kernel exactly. A mismatch causes load failure | auto-generated |
| sig_id, signer, sig_hashalgo | Module signing information (PKCS#7). Present when the module is cryptographically signed | signing step at build |
| parm | Module parameters — one line per parameter, with its description and data type | MODULE_PARM_DESC() |
Useful modinfo options
# Show only the license field
$ modinfo -F license bluetooth
# Show only the filename (useful in scripts)
$ modinfo -n bluetooth
# Show parameters with descriptions
$ modinfo -F parm e1000e
6. What changed in kernel 6.x that you should know
If you read older Linux kernel books or tutorials, you will see references to core_size and init_size fields inside the kernel’s module structure. These no longer exist in Linux 6.4 and later.
Starting with Linux 6.4, the kernel replaced a single monolithic memory layout for each module with a new per-type memory array. Each type of module memory (executable code, read-only data, writable data, init-only code, etc.) now has its own separate allocation with its own size and address. This is called the module_memory model.
The practical benefit: each memory region can now be given strict permissions independently. Executable code pages are not writable. Read-only data pages cannot be executed. This makes kernel modules significantly harder to exploit.
Interview Questions & Answers
Continue to Part 2
Now that you know how to inspect kernel modules, the next tutorial covers how to load and unload them — including what really happens when rmmod fails, how reference counting prevents data corruption, and how kernel 6.x security features affect module loading.
Part 2: Loading and Unloading Modules →