How Linux Loads Kernel Modules with Modprobe
This article explores how the Linux operating system uses the
modprobe utility to dynamically load and manage Loadable
Kernel Modules (LKMs). It covers the role of modprobe in
managing dependencies, reading configuration files, parsing the
dependency database, and passing the binary code to the kernel via
system calls. By understanding this process, administrators and
developers can better diagnose hardware driver issues, manage system
resources, and optimize boot performance.
The Role of Modprobe vs. Insmod
While Linux provides low-level utilities like insmod to
insert a single module into the kernel, insmod requires the
exact file path and fails if the module depends on symbols provided by
another un-loaded module. modprobe acts as a high-level,
intelligent front-end. It automatically discovers dependencies, loads
required prerequisite modules in the correct order, and applies
pre-configured options before loading.
Step 1: Parsing the Module Dependency Index
When a user or a system event (such as udev) invokes
modprobe <module_name>, the utility does not scan the
file system manually. Instead, it references a pre-computed dependency
tree:
- Kernel Version Directory:
modprobetargets/lib/modules/$(uname -r)/, where modules matching the running kernel version reside. - The Dependency File: It reads
modules.dep(or its binary optimized counterpartmodules.dep.bin), which is generated by thedepmodprogram. - Graph Traversal:
modprobelooks up the target module to find a list of all prerequisite.ko(Kernel Object) files. It builds a directed acyclic graph to determine the safe insertion order.
Step 2: Applying Configurations and Rules
Before inserting the modules, modprobe consults
configuration files located in:
/etc/modprobe.d/*.conf/run/modprobe.d/*.conf/usr/lib/modprobe.d/*.conf
These files define:
- Aliases: Mapping generic hardware identifiers or alternative names to specific driver modules.
- Options: Parameters passed to the module when it initializes (e.g., debug modes, buffer sizes).
- Blacklists: Directives that prevent specific modules from loading automatically.
- Install/Remove Commands: Custom shell commands that run before or after the module is inserted or removed.
Step 3: Invoking Kernel System Calls
Once dependencies are sequenced and options are prepared,
modprobe loads each required module into kernel space one
by one:
- Locating the Binary: The utility opens the
compressed or uncompressed
.kofile from disk. - System Call Execution:
modprobecalls thefinit_module()orinit_module()system call, passing the file descriptor (or memory buffer) and any specified module parameters to the kernel. - Privilege Requirement: This step requires root
privileges (
CAP_SYS_MODULEcapability).
Step 4: Kernel-Side Initialization
Once the kernel receives the system call:
- Verification: The kernel verifies the module's format (ELF standard), checks for version compatibility (vermagic), and validates cryptographic signatures if module signing is enforced.
- Memory Allocation & Relocation: Memory is
allocated in the kernel address space, and unresolved symbols are linked
to the kernel’s symbol table (
/proc/kallsyms). - Execution: The kernel executes the module’s
initialization function (defined by
module_init()in C). - Registration: The module registers its drivers, handlers, or hooks with the appropriate kernel subsystems, making it active.
Module Unloading with Modprobe
When invoked with the -r flag
(modprobe -r <module_name>), the utility reverses
this process. It queries the kernel via the delete_module()
system call. The kernel checks the module's reference count; if the
module is in use by a process or another module, removal is blocked. If
the reference count is zero, the kernel runs the cleanup function
(defined by module_exit()), unloads the code, frees the
allocated memory, and modprobe then unloads any unused
dependency modules in reverse order.