Writing Your First Linux Kernel Module

Writing a kernel module is the closest most developers get to operating system internals without becoming full-time kernel developers. This guide walks through a real module from boilerplate to a useful procfs interface.

Warning: Kernel modules run in ring 0. A bug can crash the entire system. Always test in a VM.

1. Prerequisites

# Debian/Ubuntu
sudo apt install build-essential linux-headers-$(uname -r)

# Verify
ls /lib/modules/$(uname -r)/build

2. Hello World Module

// hello.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

static int __init hello_init(void)
{
    printk(KERN_INFO "Hello, Kernel! Module loaded.\n");
    return 0;
}

static void __exit hello_exit(void)
{
    printk(KERN_INFO "Goodbye, Kernel! Module unloaded.\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple Hello World kernel module");

3. The Makefile

obj-m := hello.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

all:
	$(MAKE) -C $(KDIR) M=$(PWD) modules

clean:
	$(MAKE) -C $(KDIR) M=$(PWD) clean

Build and test:

make
sudo insmod hello.ko
lsmod | grep hello
dmesg | tail -3
sudo rmmod hello
dmesg | tail -3

4. Passing Parameters

#include <linux/moduleparam.h>

static int count = 1;
static char *name = "world";

module_param(count, int, 0);
module_param(name, charp, 0);

static int __init hello_init(void)
{
    int i;
    for (i = 0; i < count; i++)
        printk(KERN_INFO "Hello, %s! (#%d)\n", name, i);
    return 0;
}
sudo insmod hello.ko count=3 name=engineer
dmesg | tail -3

5. Creating a /proc File

#include <linux/proc_fs.h>
#include <linux/uaccess.h>
#include <linux/jiffies.h>

#define PROC_NAME "my_module_info"

static struct proc_dir_entry *proc_entry;

static ssize_t proc_read(struct file *filp, char __user *buf,
                         size_t count, loff_t *offp)
{
    char kernel_buf[256];
    int len;

    if (*offp > 0)
        return 0;

    len = snprintf(kernel_buf, sizeof(kernel_buf),
                   "Uptime (jiffies): %llu\n"
                   "Module loaded: %s\n",
                   (unsigned long long)get_seconds(),
                   "active");

    if (copy_to_user(buf, kernel_buf, len))
        return -EFAULT;

    *offp = len;
    return len;
}

static const struct proc_ops proc_fops = {
    .proc_read = proc_read,
};

static int __init hello_init(void)
{
    proc_entry = proc_create(PROC_NAME, 0444, NULL, &proc_fops);
    if (!proc_entry) {
        printk(KERN_ERR "Failed to create /proc/%s\n", PROC_NAME);
        return -ENOMEM;
    }
    printk(KERN_INFO "Module loaded. See /proc/%s\n", PROC_NAME);
    return 0;
}

static void __exit hello_exit(void)
{
    remove_proc_entry(PROC_NAME, NULL);
    printk(KERN_INFO "Module unloaded.\n");
}

Test:

sudo insmod hello.ko
cat /proc/my_module_info
sudo rmmod hello

6. Debugging Kernel Modules

dmesg -w          # follow mode — live kernel messages
lsmod             # list loaded modules
modinfo hello.ko  # module metadata

7. Kernel vs Userspace

Aspect Userspace Kernel
Memory malloc/free kmalloc/kfree
printf printf printk (dmesg)
Error handling errno Negative errno codes
Floating point Allowed Prohibited
Sleep Always allowed Depends on context
Stack size MB 4KB-16KB

Top 5 mistakes:

  1. Accessing userspace pointers directly — use copy_from_user/copy_to_user
  2. Sleeping in interrupt context — use GFP_ATOMIC
  3. Not checking return values — allocations and proc_create can fail
  4. Buffer overflows — kernel stack is 4-16KB, use kmalloc
  5. Race conditions — use spinlocks or mutexes

Start with hello-world, add procfs, then explore netfilter, character devices, or USB drivers. The kernel is not as inaccessible as it seems.