The Linux kernel is monolithic — all core services run in a single executable. But it would be impractical to recompile the entire kernel every time a new hardware device needs support. Loadable Kernel Modules (LKMs) — also called Dynamic Kernel Modules (DKMs) — solve this by allowing driver code to be loaded and unloaded from the running kernel without rebooting.
The Linux Kernel Architecture
+-------------------------------------------+
| User Space |
| (Applications, System Programs) |
+-------------------------------------------+
| System Calls
v
+-------------------------------------------+
| Kernel Space |
| +---------------------------------------+ |
| | Monolithic Kernel | |
| | (Process Mgmt, Memory, Filesystem, | |
| | Networking, Core Drivers) | |
| +---------------------------------------+ |
| | Loadable Kernel Modules (DKMs) | |
| | (loaded/unloaded at runtime) | |
+-------------------------------------------+
| Hardware Access
v
+-------------------------------------------+
| Hardware |
+-------------------------------------------+
Linux uses a monolithic kernel — unlike a microkernel, even file management systems, device drivers, and networking code run in kernel space. However, the kernel extends this with Loadable Kernel Modules.
What is a Device Driver?
A device driver is software that handles communication between hardware devices and the operating system kernel.
CPUs and I/O devices operate asynchronously. Device drivers bridge this gap using two communication models:
Parallel System
Sends one word or more per operation across multiple data lines. Suitable for high-bandwidth peripherals.
Serial System
Sends one bit per operation across a few lines. Used by UART, SPI, I2C, and other serial interfaces.
Writing a Dynamic Kernel Module
A kernel module is a C source file compiled with specific kernel headers and macros.
hello.c — A Minimal Character Device Module
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
static dev_t hello_dev;
struct cdev hello_cdev;
static char buffer[64];
ssize_t hello_read(struct file *filp, char __user *buf,
size_t count, loff_t *f_pos)
{
printk(KERN_INFO "Helloworld read (count=%d, offset=%d)\n",
(int)count, (int)*f_pos);
return 1;
}
struct file_operations hello_fops = {
.owner = THIS_MODULE,
.read = hello_read,
};
static int __init hello_module_init(void)
{
printk(KERN_INFO "Loading Helloworld_module.\n");
alloc_chrdev_region(&hello_dev, 0, 1, "hello_dev");
cdev_init(&hello_cdev, &hello_fops);
hello_cdev.owner = THIS_MODULE;
cdev_add(&hello_cdev, hello_dev, 1);
return 0;
}
static void __exit hello_module_cleanup(void)
{
printk(KERN_INFO "Cleaning-up hello_dev.\n");
cdev_del(&hello_cdev);
unregister_chrdev_region(hello_dev, 1);
}
module_init(hello_module_init);
module_exit(hello_module_cleanup);
MODULE_AUTHOR("Amr Abdelghafar");
MODULE_LICENSE("GPL");
Key module components:
| Macro / Function | Purpose |
|---|---|
module_init() | Register the module init function |
module_exit() | Register the module cleanup function |
printk() | Kernel-space logging (not printf) |
alloc_chrdev_region() | Allocate a character device number |
cdev_init() / cdev_add() | Register the character device |
MODULE_LICENSE("GPL") | Required for kernel-compatible licensing |
Building a Kernel Module with Yocto
Yocto provides the module class to streamline kernel module recipes.
Directory Structure
meta-raspberrypi/recipes-kernel/hello-mod/
├── files/
│ ├── hello.c
│ └── Makefile
└── hello_1.0.bb
BitBake Recipe: hello_1.0.bb
DESCRIPTION = "hello driver"
LICENSE = "GPLv2+"
LIC_FILES_CHKSUM = "file://${BPN}.c;endline=19;md5=4866f9824d27c1cd5324fd5e84caeb6e"
inherit module
PR = "r0"
SRC_URI = "file://Makefile file://${BPN}.c"
S = "${WORKDIR}"
The inherit module line tells BitBake to use the kernel module build class, which handles compiling the module against the correct kernel headers for the target.
Integrating the Module into the Image
Step 1: Add the recipe to the build
Create the recipe in the correct layer location:
cd ~/poky/sources
mkdir -p meta-raspberrypi/recipes-kernel/hello-mod
touch meta-raspberrypi/recipes-kernel/hello-mod/hello_1.0.bb
Step 2: Add to local.conf
Edit build/conf/local.conf to include the module in the image:
IMAGE_INSTALL:append = " hello"
Step 3: Clean previous build
bitbake -c clean rpi-basic-image
Step 4: Build the image
bitbake rpi-basic-image
License Checksum Issues
If you encounter an error like LIC_FILES_CHKSUM does not match, the md5 hash of the license header in your source file differs from the one declared in the recipe. To fix it:
- Run the build and note the actual md5 hash from the error output
- Update
LIC_FILES_CHKSUMin the.bbfile with the correct value
Loading and Unloading Modules at Runtime
On the running target device, kernel modules can be managed with standard tools:
# Load a module
insmod hello.ko
# Load with automatic dependency resolution
modprobe hello
# List loaded modules
lsmod
# Remove a module
rmmod hello
# Show module information
modinfo hello.ko
Final Thoughts
Dynamic kernel modules are the standard mechanism for device driver development in embedded Linux. They enable:
- Adding hardware support without kernel recompilation
- Modular, testable driver code
- Smaller kernel binaries (modules loaded only when needed)
Kernel Module Development Workflow:
Write module source (.c)
|
v
Create Makefile with kernel build system
|
v
Build with Yocto (inherit module)
|
v
Deploy in root filesystem image
|
v
Load on target with insmod/modprobe
|
v
Test and debug with dmesg / printk
For embedded Linux engineers, understanding kernel module development opens the door to writing custom device drivers — one of the most powerful skills in the embedded software toolkit.