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);
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
Setting KDIR to my source path.
Step 4
Build it
make
Once I got everything lined up correctly I got:
hello.ko
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
Then inside the VM:
mkdir -p /mnt/mods
mount -t 9p -o trans=virtio mods /mnt/mods
Mounting the hosts modules folder
Step 6
Insert the module
Inside the VM:
insmod /mnt/mods/hello.ko
Confirm with dmesg
dmesg | tail
in which I found
Hello: module loaded!
Along with a warning about polluting the kernel.
finally remove it.
rmmod hello
and again:
dmesg | tail
I observed
Hello: module unloaded!
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)