Writing Your First Linux Kernel Module: A Minimal Character Device

Kernel modules sound intimidating because of what they imply: code running in ring 0, no segfault safety net, a bug becomes a panic. But the ceremony to get a working module is small. Within 100 lines you can register a character device that userspace can read and write.

Here's the minimum.

What a Kernel Module Is

A .ko file the kernel can insmod at runtime. It exports two functions — init and exit — and lives in the kernel's address space until you unload it.

It cannot link against libc. The only callable symbols are kernel-exported ones (printk, kmalloc, copy_to_user, ...).

Hello, Kernel

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

MODULE_LICENSE("GPL");
MODULE_AUTHOR("You");
MODULE_DESCRIPTION("Minimal char device");

static int __init hello_init(void) {
    pr_info("hello: loaded\n");
    return 0;
}

static void __exit hello_exit(void) {
    pr_info("hello: unloaded\n");
}

module_init(hello_init);
module_exit(hello_exit);

A Makefile:

makefile
obj-m += hello.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

Build and load:

make
sudo insmod hello.ko
dmesg | tail
sudo rmmod hello

That's the skeleton. Now make it useful.

A Character Device

We'll register /dev/echo0 — writing to it stores the bytes; reading returns what was last written.

c
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/slab.h>

#define BUF_SIZE 1024

static dev_t devnum;
static struct cdev echo_cdev;
static char *buffer;
static size_t buf_len;

static ssize_t echo_read(struct file *f, char __user *ubuf,
                         size_t count, loff_t *off) {
    if (*off >= buf_len) return 0;
    if (count > buf_len - *off) count = buf_len - *off;
    if (copy_to_user(ubuf, buffer + *off, count))
        return -EFAULT;
    *off += count;
    return count;
}

static ssize_t echo_write(struct file *f, const char __user *ubuf,
                          size_t count, loff_t *off) {
    if (count > BUF_SIZE) count = BUF_SIZE;
    if (copy_from_user(buffer, ubuf, count))
        return -EFAULT;
    buf_len = count;
    return count;
}

static const struct file_operations echo_fops = {
    .owner = THIS_MODULE,
    .read  = echo_read,
    .write = echo_write,
};

Init and Cleanup

c
static int __init echo_init(void) {
    int err;
    buffer = kzalloc(BUF_SIZE, GFP_KERNEL);
    if (!buffer) return -ENOMEM;

    err = alloc_chrdev_region(&devnum, 0, 1, "echo");
    if (err) goto free_buf;

    cdev_init(&echo_cdev, &echo_fops);
    err = cdev_add(&echo_cdev, devnum, 1);
    if (err) goto unreg;

    pr_info("echo: major=%d minor=%d\n", MAJOR(devnum), MINOR(devnum));
    return 0;

unreg:
    unregister_chrdev_region(devnum, 1);
free_buf:
    kfree(buffer);
    return err;
}

static void __exit echo_exit(void) {
    cdev_del(&echo_cdev);
    unregister_chrdev_region(devnum, 1);
    kfree(buffer);
    pr_info("echo: unloaded\n");
}

Talking to It

sudo insmod echo.ko
dmesg | tail -1            # echo: major=237 minor=0
sudo mknod /dev/echo0 c 237 0
sudo chmod 666 /dev/echo0
echo "hello kernel" > /dev/echo0
cat /dev/echo0             # hello kernel

(Use udev rules in production rather than mknod by hand.)

Things You Must Get Right

Always use copy_to_user / copy_from_user. Userspace pointers can fault. A direct memcpy from a __user pointer is a kernel bug.

Match every alloc with a free in the right error path. The goto unreg; goto free_buf; ladder is idiomatic kernel C — each label undoes one step of init.

Don't sleep in atomic context. kmalloc(GFP_KERNEL) can sleep; GFP_ATOMIC cannot. If you're in an interrupt handler, only GFP_ATOMIC is safe.

Concurrency is your problem. Two userspace processes can call read/write on /dev/echo0 simultaneously. Add a mutex around buffer/buf_len for anything real:

c
static DEFINE_MUTEX(echo_lock);

No printf. Use pr_info, pr_err, etc. They route through printk and are visible via dmesg.

Where to Go Next

  • Replace mknod with a class_create + device_create pair so udev makes the node automatically.
  • Add ioctl for control commands.
  • Use proc_create or debugfs_create_file for diagnostic interfaces.
  • Read the LDD3 book's character device chapter — dated but still the clearest explanation of the model.

Takeaways

  • A loadable module is an init function, an exit function, and a license. Everything else is feature.
  • Character devices are the simplest userspace-to-kernel interface.
  • Use the __user annotation, copy_to_user, and proper error unwinding from day one.
  • Kernel C is just C with no libc and very strict cleanup discipline.