Welcome to the first post in this freebsd series, where i'll be learning and sharing the quirks and fun parts of freebsd. In this one, we'll write a custom kernel module from scratch, then load and unload it. I've always been curious about what happens under the hood, and kernel modules are a pretty direct way to find out.
01 prerequisites
You'll need freebsd 14.x installed, either on bare metal or in a vm. Personally i'd go with a vm while learning; mistakes at the kernel level can panic the whole system. You'll also need the kernel source tree. clone it with:
mkdir -p /usr/src
sudo git clone --branch releng/14.3 --depth 1 \
https://git.FreeBSD.org/src.git /usr/src
make sure you have a compiler available. check with clang --version.
if it's missing, grab it via pkg install llvm. you'll also want an
editor. Vim or nano both work fine. Make sure they are installed.
02 writing the module
create a new directory and open a new file in it:
mkdir ~/hello_world && cd ~/hello_world
vim hello_world.c
start with the headers. these give us access to the kernel types and module registration macros we need:
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
next, write the event handler. this function is called whenever the module is
loaded or unloaded. MOD_LOAD and MOD_UNLOAD are the
two events we care about:
static int
hello_world_load(module_t mod, int cmd, void *arg)
{
int error = 0;
switch (cmd) {
case MOD_LOAD:
printf("Hello World! Kernel module loaded.\n");
break;
case MOD_UNLOAD:
printf("Goodbye World! Kernel module unloaded.\n");
break;
default:
error = EOPNOTSUPP;
break;
}
return (error);
}
now declare the module metadata. this struct ties the module name to the event handler:
static moduledata_t hello_world_mod = {
"hello_world", /* module name */
hello_world_load, /* event handler */
NULL /* extra data */
};
finally, register it with the kernel. DECLARE_MODULE hooks
everything together at load time:
DECLARE_MODULE(hello_world, hello_world_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
MODULE_VERSION(hello_world, 1);
now write the Makefile. freebsd's build system handles all the
kernel-specific compiler flags for us. We don't need to configure anything
by hand:
KMOD= hello_world
SRCS= hello_world.c
.include <bsd.kmod.mk>
run make in the project directory. it will produce a
hello_world.ko file - the compiled kernel object, ready to load.
if make fails with a missing source tree error, double-check
that /usr/src was cloned correctly and that you're on the
matching release branch for your running kernel.
03 loading the module
load it with kldload:
sudo kldload ./hello_world.ko
check that it worked by looking at the kernel message buffer:
dmesg | tail -n 5
we should see Hello World! Kernel module loaded. in the output.
you can also run kldstat to list all currently loaded modules and
confirm yours is in there.
04 unloading the module
unload it with kldunload:
sudo kldunload hello_world
check the buffer again:
dmesg | tail -n 5
you should see Goodbye World! Kernel module unloaded. that's it,
we wrote a kernel module, compiled it, loaded it into a running kernel, and
unloaded it cleanly. everything from here gets more interesting.
next up in the series: writing readme, configuring git for the project, and some necessary git commands that makes our life easier.