In the past, updating the Linux kernel to include new features or support for additional hardware was a common practice, with users needing to recompile the kernel to suit their needs. Those days are mostly gone, thanks to the advent of Loadable Kernel Modules (LKMs). LKMs allow users to add functionality or new hardware support on-the-fly, eliminating the need for a system reboot or kernel rebuild.
While regular applications in Linux operate with limited system privileges, Kernel and its modules work at a level that grants full access to the system’s memory and I/O operations. This is why module debugging is considered dangerous—it can potentially compromise system stability. Using a virtual machine during development is a common practice to mitigate risks and protect the main system from the fallout of any unstable module.
An Historical Snapshot
Linux has been supporting modules since its 1.2 release. The present-day kernel has the capacity to include functionality either directly or through modules. It wouldn’t make sense to embed every device driver directly into the kernel. Modules allow you to compile various drivers and activate only the ones needed at any given time.
LKMs are essentially the backbone for managing drivers for devices, file systems, and networks. Additionally, they can add new functionalities or modify existing system calls, handle TTY line protocols, and orchestrate executable file operations.
Module Management Primer
To inspect the currently loaded modules, the lsmod
command is used. Modules may work in standalone mode or have dependencies on others. The main commands for loading modules in the kernel are insmod
and modprobe
, with the latter handling dependency resolution. To remove modules, one employs rmmod
, provided they aren’t in use. Note that module addition or removal requires superuser rights, although lsmod
can be executed by standard users. To delve into module dependencies and properties, tools like depmod
and modinfo
are useful.
Module Crafting Essentials
Creating a custom module is surprisingly accessible. A simple module that logs its loading and unloading can illustrate the process. Furthermore, you can specify a parameter when the module is loaded, which then becomes visible in the kernel log.
This simplified source code includes directives and macros that provide details to the modinfo
command. Variables like somedata
are used only during initialization, whereas key
can be set with insmod
and is accessible by the root user through the Linux file system hierarchy. The code concludes with the registration of initiation and cleanup events for the module, including logging actions for both.
Information is conveyed to the kernel log via two methods: the standard printk
function and the more recent pr_info
macro, which builds upon printk
. Consistency in their use within your module code is crucial. To compile your module, you’ll need the appropriate makefile and kernel headers. The end product is a .ko file that can be loaded and tested for functionality and parameter modification.
The Deceptive Simplicity
On the surface, it seems quite straightforward. However, the complexity increases substantially when considering the detailed interaction your module has with both the kernel and the underlying hardware. To expand upon our initial example, consider a module that generates a file in the /proc filesystem
. The modified code would look something like this:
After loading the module with insmod
, its output can be observed by executing cat /proc/jollywrencher
, revealing an ASCII art representation of the Hackaday logo.
This rudimentary illustration is merely the beginning, with more involved kernel modules being a possibility. The full source code can often be found online, serving as an informative platform for those new to kernel module development.
Image Credit: MaIII Themd / Shutterstock
