5.3 Kernel Modules: Dynamic Extension of Kernel Functionality
Right, so the kernel. It’s this magnificent, monolithic beast that runs the whole show. But if you had to recompile the entire kernel and reboot your machine every time you needed to add support for a new weird USB gadget or a filesystem you’ll use once, you’d probably just give up and go live in a cabin in the woods. The designers of Linux, being slightly more sociable than that, came up with a brilliant solution: kernel modules. These are pieces of code that can be dynamically loaded into and unloaded from a running kernel, on demand, without a reboot. It’s like being able to hot-swap the engine of your car while you’re still driving down the highway. It’s a bit absurd when you think about it, and it’s absolutely brilliant.
The key thing to understand is that a module isn’t a separate process; it becomes part of the kernel. It runs with full kernel privileges, has access to all the internal data structures and functions, and if it crashes, it takes the entire system down with it. No pressure.
The Absolute Basics: Hello, Kernel World
Let’s start with the canonical example, the hello world of kernel modules. This is the simplest thing that could possibly work. Create a file called hello.c:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name Here");
MODULE_DESCRIPTION("A simple, vapid hello world module");
static int __init hello_init(void) {
printk(KERN_INFO "Hello, world!\n");
return 0;
}
static void __exit hello_exit(void) {
printk(KERN_INFO "Goodbye, cruel world!\n");
}
module_init(hello_init);
module_exit(hello_exit);
And a Makefile to build it:
obj-m += hello.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
Now run make. If the kernel gods are smiling, you’ll get a hello.ko file (KO for Kernel Object). Now for the magic:
sudo insmod hello.ko to load it. It will print “Hello, world!” to the kernel ring buffer. Check dmesg or journalctl -k to see it. Then, sudo rmmod hello to unload it, and check dmesg again for its melancholic farewell.
Why __init and __exit? Those are hints to the compiler about the memory footprint. The init function is run once at load time and can then be discarded from memory. The exit function is obviously only needed when you’re unloading the module. It’s a small but thoughtful optimization.
How the Kernel Finds and Manages Modules
You’ve probably used modprobe and lsmod and wondered where the heck the kernel keeps track of all this. When you run insmod, you’re giving it a full path. But modprobe is smarter; it understands dependencies. The secret is /lib/modules/$(uname -r)/, which contains a modules.dep file—a map built by running depmod. This file tells modprobe that if you want to load module B, which uses symbols from module A, it needs to load A first. lsmod is just a pretty-printer for the content of the /proc/modules pseudo-file, which is the kernel’s real-time list of currently loaded modules.
The Devil’s in the Details: Symbols and Visibility
Here’s where it gets interesting. Your module lives in its own little world. It can’t just call any kernel function it wants to by default. Those functions and variables need to be explicitly exported by the kernel or other modules. Think of it as a form of namespacing to prevent absolute chaos.
You can see all exported symbols with cat /proc/kallsyms. To export a symbol from your own module so others can use it, you use the EXPORT_SYMBOL() or EXPORT_SYMBOL_GPL() macros. The _GPL variant enforces that only other GPL-licensed modules can use your symbol, a legal choice the kernel developers made to try and keep things open. It’s a bit of a honor system with teeth.
int my_exported_variable = 42;
EXPORT_SYMBOL(my_exported_variable);
void my_exported_function(void) {
/* ... do something brilliant ... */
}
EXPORT_SYMBOL(my_exported_function);
A common pitfall? Trying to use a symbol that isn’t exported. The module will compile happily, but it will fail to load with a cryptic “Unknown symbol” error. Always check your dependencies.
The Subtle Art of Not Crashing the Box
Writing modules is a tightrope walk without a net. There is no segfault; there is only panic. Here are the rules:
- No User-Space Allowed: You can’t call standard C library functions like
printf()ormalloc(). You useprintk()for output andkmalloc()/kfree()for memory management. The kernel has its own entire universe of functions for everything. - Check Every Return Value: In user space, forgetting to check if
malloc()failed is sloppy. In kernel space, it’s a suicide note. You must check the return value of every function that can fail (likekmalloc,request_irq, etc.) and clean up gracefully. - Cleanup is Not an Afterthought: Your
initfunction’s job is to set up success. Yourexitfunction’s job is to undo everything theinitfunction did, in reverse order. If you register a device, you must unregister it. If you allocate memory, you must free it. If you request an interrupt line, you must free it. The kernel expects you to leave no trace. A sloppyexitfunction is a one-way ticket to a memory leak or a crashed system on the next module load.
The design is powerful but also a bit questionable in its trust. It assumes module authors are competent and meticulous. The results, as you can imagine, are… mixed. That’s why you’ll see so much boilerplate goto error handling in kernel code—it’s the cleanest way to unwind a complex initialization sequence. It looks ugly, but it’s the right tool for the job.
Mastering modules is the first real step to understanding how the kernel is built—not as a monolith, but as a collaborative, dynamic ecosystem of components. And you just added your first one to it. Not bad.