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:

  1. Kernel Version Directory: modprobe targets /lib/modules/$(uname -r)/, where modules matching the running kernel version reside.
  2. The Dependency File: It reads modules.dep (or its binary optimized counterpart modules.dep.bin), which is generated by the depmod program.
  3. Graph Traversal: modprobe looks 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:

These files define:

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:

  1. Locating the Binary: The utility opens the compressed or uncompressed .ko file from disk.
  2. System Call Execution: modprobe calls the finit_module() or init_module() system call, passing the file descriptor (or memory buffer) and any specified module parameters to the kernel.
  3. Privilege Requirement: This step requires root privileges (CAP_SYS_MODULE capability).

Step 4: Kernel-Side Initialization

Once the kernel receives the system call:

  1. Verification: The kernel verifies the module's format (ELF standard), checks for version compatibility (vermagic), and validates cryptographic signatures if module signing is enforced.
  2. Memory Allocation & Relocation: Memory is allocated in the kernel address space, and unresolved symbols are linked to the kernel’s symbol table (/proc/kallsyms).
  3. Execution: The kernel executes the module’s initialization function (defined by module_init() in C).
  4. 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.