Exploring Operating Systems

Day 34: Linux Kernel Module Development

Table of Contents

  1. Introduction to Kernel Modules
  2. Kernel Module Architecture
  3. Setting Up Development Environment
  4. Basic Kernel Module Structure
  5. Module Parameters
  6. Character Device Driver Example
  7. Debugging Kernel Modules
  8. Best Practices and Security
  9. Conclusion

1. Introduction to Kernel Modules

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.

Key Concepts

2. Kernel Module Architecture

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);

Core Components

3. Setting Up Development Environment

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.

Required Packages and Tools

sudo apt-get update
sudo apt-get install build-essential linux-headers-$(uname -r)

Directory Structure

module_project/
├── Makefile
├── module_main.c
└── module_utils.h

Basic Makefile

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

4. Basic Kernel Module Structure

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);

Explanation

Running the Code

  1. Save the code in a file named hello_module.c.
  2. Create a Makefile as shown in the previous section.
  3. Run make to compile the module.
  4. Load the module using sudo insmod hello_module.ko.
  5. Check the kernel log using dmesg to see the “Hello, Kernel!” message.
  6. Unload the module using sudo rmmod hello_module.
  7. Check the kernel log again to see the “Goodbye, Kernel!” message.

5. Module Parameters

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");

Explanation

Running the Code

  1. Add the parameter definitions to your module code.
  2. Compile the module using make.
  3. Load the module with parameters: sudo insmod hello_module.ko value=100 name="test".
  4. Check the parameter values in /sys/module/hello_module/parameters/.

6. Character Device Driver Example

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");

What are we doing in the above code?

Running the Code

  1. Save the code in a file named chardev.c.
  2. Compile the module using make.
  3. Load the module using sudo insmod chardev.ko.
  4. Check the kernel log using dmesg to see the “Character device registered” message.
  5. Interact with the device using a user-space program or commands like cat /dev/chardev.
  6. Unload the module using sudo rmmod chardev.
  7. Check the kernel log again to see the “Character device unregistered” message.

7. Debugging Kernel Modules

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 Levels

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");

Using KGDB

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

8. Best Practices and Security

When developing kernel modules, it’s important to follow best practices to ensure reliability and security.

9. Conclusion

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.