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
#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:
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.
#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
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:
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
mknodwith aclass_create+device_createpair so udev makes the node automatically. - Add
ioctlfor control commands. - Use
proc_createordebugfs_create_filefor 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
__userannotation,copy_to_user, and proper error unwinding from day one. - Kernel C is just C with no libc and very strict cleanup discipline.