all posts

load and unload your first kernel module in freebsd

a walkthrough of writing a minimal kernel module from scratch, compiling it, and loading it cleanly into a running freebsd system.

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:

shell
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:

shell
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:

c
#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:

c
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:

c
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:

c
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:

makefile
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:

shell
sudo kldload ./hello_world.ko

check that it worked by looking at the kernel message buffer:

shell
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:

shell
sudo kldunload hello_world

check the buffer again:

shell
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.

anjil · kathmandu · 2026