Loadable Kernel Modules (LKMs) are object files that contain code to extend the running kernel of an operating system. They provide a way to add functionality to the kernel without recompiling the kernel or rebooting the system.
insmod
, the kernel calls the module’s initialization function, which registers the module’s functionality.rmmod
, the kernel calls the module’s cleanup function, which unregisters the module’s functionality.The architecture of kernel modules is built around several key components:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Description of your module");
MODULE_VERSION("1.0");
static int __init module_init_function(void) {
printk(KERN_INFO "Module initialized\n");
return 0;
}
static void __exit module_exit_function(void) {
printk(KERN_INFO "Module removed\n");
}
module_init(module_init_function);
module_exit(module_exit_function);
module_init()
macro specifies the function to be called when the module is loaded, and module_exit()
specifies the function to be called when the module is unloaded.MODULE_LICENSE
, MODULE_AUTHOR
, MODULE_DESCRIPTION
, and MODULE_VERSION
macros provide metadata about the module. This information is useful for documentation and debugging.EXPORT_SYMBOL
macro.module_param()
macro.To develop kernel modules, you need to set up a proper development environment. This includes installing the necessary packages and setting up a directory structure.
sudo apt-get update
sudo apt-get install build-essential linux-headers-$(uname -r)
module_project/
├── Makefile
├── module_main.c
└── module_utils.h
obj-m += module_name.o
module_name-objs := module_main.o module_utils.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
obj-m
variable specifies the object files to be built as kernel modules. The make -C
command invokes the kernel build system to compile the module.module_main.c
file contains the main code for the module, and module_utils.h
contains any utility functions or definitions. The Makefile is used to compile these files into a kernel module.Let’s create a complete working example of a simple “Hello, Kernel!” module.
// you can save it as hello_module.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple Hello World module");
MODULE_VERSION("1.0");
static int __init hello_init(void) {
printk(KERN_INFO "Hello, Kernel!\n");
return 0;
}
static void __exit hello_exit(void) {
printk(KERN_INFO "Goodbye, Kernel!\n");
}
module_init(hello_init);
module_exit(hello_exit);
hello_init()
function is called when the module is loaded. It prints a message to the kernel log using printk()
.hello_exit()
function is called when the module is unloaded. It also prints a message to the kernel log.MODULE_*
macros provide information about the module, such as its license, author, description, and version.hello_module.c
.make
to compile the module.sudo insmod hello_module.ko
.dmesg
to see the “Hello, Kernel!” message.sudo rmmod hello_module
.Module parameters allow you to pass configuration options to a module when it is loaded. This is useful for configuring the behavior of the module at runtime.
#include <linux/module.h>
#include <linux/moduleparam.h>
static int value = 42;
static char *name = "default";
module_param(value, int, 0644);
MODULE_PARM_DESC(value, "An integer value");
module_param(name, charp, 0644);
MODULE_PARM_DESC(name, "A character string");
module_param()
macro defines a parameter that can be passed to the module when it is loaded. The parameters can be of various types, such as int
, charp
(character pointer), etc.0644
argument specifies the permissions for the parameter file in sysfs. This allows you to read and write the parameter from user space.MODULE_PARM_DESC
macro provides a description of the parameter, which is useful for documentation.make
.sudo insmod hello_module.ko value=100 name="test"
./sys/module/hello_module/parameters/
.A character device driver is a type of kernel module that allows user-space programs to interact with hardware devices. Here’s a complete example of a simple character device driver.
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#define DEVICE_NAME "chardev"
#define CLASS_NAME "char_class"
static int major_number;
static struct class *char_class = NULL;
static struct cdev char_cdev;
static int device_open(struct inode *inode, struct file *file) {
printk(KERN_INFO "Device opened\n");
return 0;
}
static int device_release(struct inode *inode, struct file *file) {
printk(KERN_INFO "Device closed\n");
return 0;
}
static ssize_t device_read(struct file *file, char __user *buffer,
size_t length, loff_t *offset) {
char message[] = "Hello from kernel\n";
size_t message_len = strlen(message);
if (*offset >= message_len)
return 0;
if (length > message_len - *offset)
length = message_len - *offset;
if (copy_to_user(buffer, message + *offset, length))
return -EFAULT;
*offset += length;
return length;
}
static ssize_t device_write(struct file *file, const char __user *buffer,
size_t length, loff_t *offset) {
printk(KERN_INFO "Received %zu bytes\n", length);
return length;
}
static struct file_operations fops = {
.open = device_open,
.release = device_release,
.read = device_read,
.write = device_write,
};
static int __init chardev_init(void) {
// allocate major number
major_number = register_chrdev(0, DEVICE_NAME, &fops);
if (major_number < 0) {
printk(KERN_ALERT "Failed to register major number\n");
return major_number;
}
// register device class
char_class = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(char_class)) {
unregister_chrdev(major_number, DEVICE_NAME);
printk(KERN_ALERT "Failed to register device class\n");
return PTR_ERR(char_class);
}
// register device driver
if (IS_ERR(device_create(char_class, NULL, MKDEV(major_number, 0),
NULL, DEVICE_NAME))) {
class_destroy(char_class);
unregister_chrdev(major_number, DEVICE_NAME);
printk(KERN_ALERT "Failed to create device\n");
return PTR_ERR(char_class);
}
printk(KERN_INFO "Character device registered\n");
return 0;
}
static void __exit chardev_exit(void) {
device_destroy(char_class, MKDEV(major_number, 0));
class_unregister(char_class);
class_destroy(char_class);
unregister_chrdev(major_number, DEVICE_NAME);
printk(KERN_INFO "Character device unregistered\n");
}
module_init(chardev_init);
module_exit(chardev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple character device driver");
MODULE_VERSION("1.0");
file_operations
structure defines the functions that handle operations like opening, reading, writing, and closing the device.register_chrdev()
function registers the character device with the kernel. The class_create()
and device_create()
functions create the device class and device node in /dev
.chardev_exit()
function unregisters the device and cleans up resources when the module is unloaded.chardev.c
.make
.sudo insmod chardev.ko
.dmesg
to see the “Character device registered” message.cat /dev/chardev
.sudo rmmod chardev
.Debugging kernel modules can be challenging because they run in kernel space. However, there are several techniques you can use to debug them effectively.
printk(KERN_EMERG "System is unusable\n");
printk(KERN_ALERT "Action must be taken immediately\n");
printk(KERN_CRIT "Critical conditions\n");
printk(KERN_ERR "Error conditions\n");
printk(KERN_WARNING "Warning conditions\n");
printk(KERN_NOTICE "Normal but significant\n");
printk(KERN_INFO "Informational\n");
printk(KERN_DEBUG "Debug-level messages\n");
KGDB is a kernel debugger that allows you to debug the kernel using GDB. To use KGDB, you need to enable it in the kernel configuration and boot the kernel with the appropriate parameters.
# enable KGDB in kernel config
CONFIG_KGDB=y
CONFIG_KGDB_SERIAL_CONSOLE=y
# boot parameters
kgdboc=ttyS0,115200 kgdbwait
printk()
function allows you to log messages at different levels of severity. These messages can be viewed using dmesg
.When developing kernel modules, it’s important to follow best practices to ensure reliability and security.
Kernel module development is a complex but powerful aspect of Linux system programming. This guide covered the fundamentals of creating, loading, and managing kernel modules, including character device drivers and debugging techniques. The provided examples and best practices should serve as a foundation for developing reliable kernel modules.
By following the steps outlined in this guide, you should be able to create, compile, load, and debug your own kernel modules. Remember to always test your modules thoroughly and follow best practices to ensure the stability and security of your system.