Kernel modules are object files used to extend an operating system’s
kernel functionality at run time.
In this post, we’ll look at implementing a simple character device driver as a
kernel module in NetBSD.
Once it is loaded, userspace processes will be able to write
an arbitrary byte string to
the device,
and on every successive read
expect a cryptographically-secure pseudorandom permutation of
the original byte string.
Before we begin, compiling a kernel module requires the NetBSD source code to live in /usr/src
.
This explains how to
get that.
Usually, most userspace interfaces to character or block devices are through
special files that live in /dev
. We’ll create one such special file through
the command
$ mknod /dev/rperm c 420 0
The c
indicates that this file is an interface
to a character device, 420
indicates this device’s major number, and 0
indicates this device’s minor number. The major number is used by the kernel
to uniquely identify each device, and the minor number is usually used
internally by device drivers but we won’t be bothering with it.
Our device driver will specifically implement the open
, read
, write
, and close
I/O methods. To
register our implementations of those methods with the kernel, we first
prototype them in way that makes the compiler happy using the
dev_type_*
set of macros, and then put them into a struct cdevsw
.
dev_type_open(rperm_open);
dev_type_close(rperm_close);
dev_type_write(rperm_write);
dev_type_read(rperm_read);
static struct cdevsw rperm_cdevsw = {
.d_open = rperm_open,
.d_close = rperm_close,
.d_read = rperm_read,
.d_write = rperm_write,
.d_ioctl = noioctl,
.d_stop = nostop,
.d_tty = notty,
.d_poll = nopoll,
.d_mmap = nommap,
.d_kqfilter = nokqfilter,
.d_discard = nodiscard,
.d_flag = D_OTHER
};
As we can see, there are plenty of functions we won’t be implementing.
devsw
stands for device switch.
Every kernel module is required to define it’s metadata through the C macro
MODULE(class, name, required)
. Since our module is a device driver, named
rperm
, and won’t require another module being pre-loaded, we write
MODULE(MODULE_CLASS_DRIVER, rperm, NULL);
Every module is also required to implement a MODNAME_modcmd
function, which the kernel
calls to report important module-related events, like when the module loads
or unloads. This is where we’ll register our struct cdevsw
.
#define CMAJOR 420
static int
rperm_modcmd(modcmd_t cmd, void *args)
{
devmajor_t bmajor, cmajor;
bmajor = -1;
cmajor = CMAJOR;
switch(cmd) {
case MODULE_CMD_INIT:
devsw_attach("rperm", NULL, &bmaj