The LKM Framework — Extending the Kernel at Runtime-Free Linux kernel development course

Previous Lecture
Next Lecture

Linux Kernel Programming
Chapter 4  |  Part 2 of 6
The LKM Framework — Extending the Kernel at Runtime
📚 Beginner Friendly
⏱ ~20 min read
🔐 Kernel 6.x Updated

🔍 Topics Covered
What is an LKM
Built-in vs Module
Module Lifecycle
init and exit functions
.ko file format
Module dependencies
GPL License requirement
Module metadata

🎯 Why Learn LKMs?

Almost every device driver you will ever write for Linux is a kernel module. Understanding the LKM framework is not optional — it is the entry point to all of kernel development. This tutorial builds a clear mental model before you write a single line of code.

🔌 What Exactly is a Loadable Kernel Module?

A Loadable Kernel Module (LKM) is a piece of compiled kernel code that can be inserted into a running kernel and removed from it — all without rebooting the system. Think of it as a plug-in for the kernel.

When you insert a module, it becomes part of the kernel. It runs in kernel space with full privileges. It can use any exported kernel symbol, register itself with kernel subsystems, talk to hardware, and do everything that built-in kernel code can do.

When you remove a module, the kernel reclaims the memory it used and the functionality is gone — cleanly, as if it was never there.

What a Kernel Module (.ko) File Contains
mydriver.ko
.text — your module’s compiled code
.data — initialized global variables
.bss — uninitialized global variables
.modinfo — author, license, version, description
.gnu.linkonce.this_module — module struct
.symtab — symbol table (exported symbols)
.rela.* — relocation entries (resolved at load)
ELF object file — NOT a standalone executable

A .ko file is a special ELF (Executable and Linkable Format) object file. It is not a standalone program — it cannot run by itself. It must be linked into the running kernel by the kernel’s module loader.

🔣 Built-in Kernel Code vs Loadable Modules

When you configure the Linux kernel (using make menuconfig), for each feature or driver you have three choices:

Y (Yes) — Built into the kernel image: The code is compiled directly into the vmlinux kernel image. It is always present, always consuming memory. There is no option to remove it while the kernel is running.
M (Module) — Compiled as an LKM: The code is compiled into a separate .ko file. It is only loaded into memory when needed and can be removed when not needed. This keeps the base kernel image small.
N (No) — Not compiled at all: The feature is completely excluded. No binary exists for it.

Built-in vs LKM — Side by Side
Property Built-in (Y) LKM (M)
Load time Always loaded at boot Loaded on demand
Memory Always consuming RAM Only when loaded
Can remove at runtime No Yes (rmmod)
Update without reboot No — need full rebuild Yes — rmmod + insmod
Performance Slightly faster (no relocation) Negligible overhead
Typical use Core subsystems (scheduler, VFS core) Device drivers, filesystems, network protocols

⚙ The LKM Lifecycle — From Source to Running Code

A kernel module goes through a well-defined lifecycle. Understanding each phase helps you debug issues and write cleaner code.

LKM Lifecycle
1. Write
module.c — your source code
Makefile — build instructions
2. Build
make using the kernel build system
Output: module.ko
3. Load (insmod / modprobe)
Kernel verifier checks the module.
Relocations applied. Memory allocated.
init function called.
4. Running
Module is live inside the kernel.
Responds to hardware events, system calls,
or timer callbacks.
5. Unload (rmmod / modprobe -r)
exit function called.
Resources freed. Memory returned to kernel.
Module removed from kernel’s module list.

The two most important moments in a module’s life are when its init function is called (at load time) and its exit function is called (at unload time). You are responsible for these two functions — they are the heart of every kernel module you write.

📄 The Two Mandatory Functions: init and exit

Every kernel module has (at minimum) two functions:

Module Init Function

Called once when the module is loaded with insmod. This is where you allocate resources, register with kernel subsystems (like character device subsystem, interrupt handlers, etc.), and set up your hardware. If this function returns 0, the load succeeds. If it returns a negative error code, the load fails and the kernel will not add the module.

Module Exit Function

Called once when the module is removed with rmmod. This is where you undo everything the init function did — free memory, deregister from subsystems, stop timers, release IRQs. If you forget to clean up something, you will have a resource leak in the kernel that persists until the next reboot.

The macros module_init() and module_exit() register your functions with the kernel. You will see exactly how to use these in Part 3 when we write our first module.

Init / Exit Registration Pattern
/* These are the two key macros that connect your functions
   to the kernel's module framework */

module_init(my_init_function);   /* called on insmod */
module_exit(my_exit_function);   /* called on rmmod  */

/*
 * The init function MUST return:
 *   0         — success, module loaded
 *   negative  — failure (e.g. -ENOMEM, -ENODEV)
 *
 * The exit function has no return value.
 * It MUST undo everything init did.
 */

📋 Module Metadata — License, Author, Description

Every module should declare some metadata using standard macros. This information is embedded in the .ko file and can be read with modinfo.

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name <your@email.com>");
MODULE_DESCRIPTION("A short description of what this module does");
MODULE_VERSION("1.0");

The MODULE_LICENSE() macro is especially important. The Linux kernel exports many of its internal symbols only to modules that declare a GPL-compatible license. If you use MODULE_LICENSE("Proprietary"), the kernel will mark itself as “tainted” when you load the module, and many symbols (especially those marked EXPORT_SYMBOL_GPL) will be unavailable to your module. For learning and open-source work, always use "GPL".

What modinfo shows for a module
$ modinfo mymodule.ko

filename:       /path/to/mymodule.ko
description:    A learning kernel module
author:         Your Name <your@email.com>
license:        GPL
version:        1.0
srcversion:     A3F5B2C1D9E4F0A1B2C3D4E
depends:
retpoline:      Y
name:           mymodule
vermagic:       6.8.0-45-generic SMP preempt mod_unload modversions

Notice the vermagic field. This contains the exact kernel version and configuration flags the module was compiled for. The kernel checks this when you try to load the module — if it does not match the running kernel, the load will fail. This is why you must always compile your module against the headers of the kernel you intend to run it on.

📁 Where Kernel Modules Live on Your System

Kernel modules that ship with your Linux distribution are stored under:

/lib/modules/$(uname -r)/

# Example:
/lib/modules/6.8.0-45-generic/
├── kernel/
│   ├── drivers/
│   │   ├── net/        ← network driver modules
│   │   ├── usb/        ← USB driver modules
│   │   └── ...
│   ├── fs/             ← filesystem modules
│   └── ...
├── modules.dep         ← dependency information
├── modules.alias       ← aliases for auto-loading
└── modules.builtin     ← list of built-in (non-module) features

The modules.dep file is critical — it tells modprobe which modules depend on which other modules. When you do modprobe mydriver, it reads this file and automatically loads all dependencies first.

After installing a new module (using make install or copying it manually), run depmod -a to regenerate these dependency files so modprobe can find your module.

🏆 Interview Questions — LKM Framework
Q1. What is the difference between a loadable kernel module and built-in kernel code?
Built-in code (compiled with Y) is always present in the kernel image and loaded at boot. An LKM (compiled with M) is a separate .ko file that can be loaded and unloaded at runtime without rebooting. LKMs allow the base kernel to stay small while adding functionality on demand.
Q2. What does module_init() do?
It registers the init function with the kernel module framework. When the module is loaded using insmod or modprobe, the kernel calls this registered function. The function must return 0 on success or a negative error code on failure.
Q3. What is vermagic and why does it matter?
vermagic is a string embedded in every .ko file that identifies the exact kernel version and build configuration it was compiled for. When loading a module, the kernel checks if its vermagic matches the running kernel. A mismatch causes the load to fail. This prevents loading modules built for a different kernel, which could cause crashes.
Q4. Why is MODULE_LICENSE(“GPL”) important?
The kernel marks many internal symbols with EXPORT_SYMBOL_GPL, making them available only to GPL-licensed modules. If a module declares a proprietary license, it cannot access these symbols and the kernel will be flagged as “tainted,” meaning its state is not fully trusted. GPL licensing also ensures legal compatibility with the kernel.
Q5. What must the exit function always do?
The exit function must undo everything the init function did. This means deregistering from all kernel subsystems, freeing all allocated memory (kmalloc, vmalloc), releasing IRQs, stopping timers, and removing any /proc or sysfs entries. Forgetting to clean up causes kernel resource leaks that persist until reboot.
Q6. What happens if a module’s init function returns a non-zero value?
The module load fails. The kernel does not add the module to its list of loaded modules, and the exit function is not called. Any resources allocated before the failure point in the init function must be cleaned up within the init function itself before returning the error code — otherwise they will leak.

Up Next: Part 3 — Writing Your First Kernel Module
Let us write, build, and load a real Hello World kernel module step by step.

Previous Lecture
Next Lecture

Leave a Reply

Your email address will not be published. Required fields are marked *