DEV Community

Adam Weber
Adam Weber

Posted on

Kernel Module Dev Environment

I’ve been poking at kernel development recently; partly for fun, partly for career growth, partly because I want to understand the layers I’ve leaned on for years.

This post continues that thread: small, incremental steps, written as reminders to myself and hopefully helpful to anyone taking a similar path.

This time, I wanted to get a kernel module development environment working end-to-end. Nothing glamorous. No drivers. No deep dives into subsystems. Just:

Build the kernel
Boot QEMU with it
Build a module
Insert it
Watch it say hello
Remove it

As usual, half the battle is just wiring together all the moving pieces correctly. So this post documents what I did, what worked, and what I want to remember later.

Step 1

Set up a dedicated folder for modules

First lesson: keep module work isolated so you don’t end up polluting the kernel tree.

mkdir -p ~/kernel/hello
cd ~/kernel/hello

Good fences make good neighbors.

Step 2

Write the simplest possible module

Kernel modules are surprisingly small once you strip away the noise. Here’s the entire thing:

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

MODULE_LICENSE("GPL");
MODULE_AUTHOR("AWs.");
MODULE_DESCRIPTION("Hello world kernel module");
MODULE_VERSION("0.1");

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

static void __exit hello_exit(void)
{
    pr_info("Hello: module unloaded!\n");
}

module_init(hello_init);
module_exit(hello_exit);

Enter fullscreen mode Exit fullscreen mode

It loads, prints a message, and unloads. Found in console output as well as dmesg.

One thing I wanted to remind myself here:
Userland logging is not kernel logging.

Use pr_info() instead of printf(). Muscle memory is strong, but so is printk. Note to self, explore the rest of the pr_* macros.

Step 3

Use the kernel’s build system.

Create a Makefile:

obj-m += hello.o

KDIR := /path/to/your/kernel/source
PWD := $(shell pwd)

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

clean:
    $(MAKE) -C $(KDIR) M=$(PWD) clean
Enter fullscreen mode Exit fullscreen mode

Setting KDIR to my source path.

Step 4

Build it

make
Enter fullscreen mode Exit fullscreen mode

Once I got everything lined up correctly I got:

hello.ko
Enter fullscreen mode Exit fullscreen mode

Step 5

Get the module into QEMU (virtio-fs)

The nicest way to test that I could find was adding the following to my qemu alias. There is almost certainly a better way to do this, such that I won't have to modify it for other modules.

-fsdev local,id=mods,path=~/kernel-dev/hello,security_model=none \
-device virtio-9p-pci,fsdev=mods,mount_tag=mods
Enter fullscreen mode Exit fullscreen mode

Then inside the VM:

mkdir -p /mnt/mods
mount -t 9p -o trans=virtio mods /mnt/mods
Enter fullscreen mode Exit fullscreen mode

Mounting the hosts modules folder

Step 6

Insert the module

Inside the VM:

insmod /mnt/mods/hello.ko
Enter fullscreen mode Exit fullscreen mode

Confirm with dmesg

dmesg | tail
Enter fullscreen mode Exit fullscreen mode

in which I found

Hello: module loaded!
Enter fullscreen mode Exit fullscreen mode

Along with a warning about polluting the kernel.

finally remove it.

rmmod hello
Enter fullscreen mode Exit fullscreen mode

and again:

dmesg | tail
Enter fullscreen mode Exit fullscreen mode

I observed

Hello: module unloaded!
Enter fullscreen mode Exit fullscreen mode

What this means for me:

I built my own kernel
I booted it
I built a module against it
I loaded that module into the kernel
I then unloaded it from the kernel

This is my pipeline for future kernel work. If you have experience let me know any tips or changes you think I should make.

What I’m doing next:

Probably /proc/hello or a sysfs attribute. Maybe standing up a minimal character driver.

The working dev environment was the real milestone.

If you’re doing your own kernel experiments, I’d love to hear what you’re building.

Top comments (0)