Built-in vs Module
Module Lifecycle
init and exit functions
.ko file format
Module dependencies
GPL License requirement
Module metadata
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.
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.
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.
When you configure the Linux kernel (using make menuconfig), for each feature or driver you have three choices:
vmlinux kernel image. It is always present, always consuming memory. There is no option to remove it while the kernel is running..ko file. It is only loaded into memory when needed and can be removed when not needed. This keeps the base kernel image small.| 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 |
A kernel module goes through a well-defined lifecycle. Understanding each phase helps you debug issues and write cleaner code.
Makefile — build instructions
make using the kernel build systemOutput:
module.koRelocations applied. Memory allocated.
init function called.
Responds to hardware events, system calls,
or timer callbacks.
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.
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.
/* 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.
*/
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".
$ 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.
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.
