My intent with this, and related articles to come, is to explain how I develop software that runs as part of the Linux kernel. Not so much the way my code works, but rather the tools, configuration, and techniques that allow me to create kernel code quickly (within reason).

My hope is that you can use some of these ideas to bring you closer to doing actual work, and spend less time monkeying around with your environment. Or at least this can serve as a reference so I don’t keep reinventing every wheel I come across.

Techniques like testing your module within UML (Usermode Linux) so you don’t destroy your machine when things go wrong, or how to use objdump and some basic algebra to determine exactly where an oops occurred in a given piece of code. Perhaps we could cover common bit-shift operations and how they’re used to save computational resources. These and other basic debugging tools can help you navigate the world of kernel development more quickly and increase productivity immensely.

This exercise will walk you through creating a simple module, compiling it, loading it into your running kernel, and verifying that it has been loaded. This is the most basic form of the Modify-Compile-Test loop that allows you to begin to iterate over a piece of code. Also, I have to get this intro stuff out of the way so I can later talk about things that build upon it.

I assume a good general understanding of the Linux ecosystem as well a proficiency in C and other related tools like make, gdb, and vim (or something equivalent).

Your First Module

Create a directory to hold your kernel source

$ mkdir ~/my-module
$ cd ~/my-module

Let’s start with a boilerplate makefile. Put the following in your Makefile

ifneq ($(KERNELRELEASE),)

        obj-m += my-module.o

else

        KERNELDIR ?= /lib/modules/$(shell uname -r)/build
        PWD := $(shell pwd)

all: my-module

%.o: %.c
        @echo "  CC      $<"
        @$(CC) -D_GNU_SOURCE -ggdb3 -Wall -Wextra -I. -c $< -o $@

my-module:
        $(MAKE) -C $(KERNELDIR) M=$(PWD) modules

tags: my-module.c
        @echo "  GEN     $@"
        @ctags -R

clean:
        $(MAKE) -C $(KERNELDIR) M=$(PWD) clean
        rm -f *.o tags

endif

Make sure to preserve the indentation using tabs or make will complain.

The above makefile gives you three targets. An all target (just typing make will launch this) that builds your module. A tags target that will generate a ctags file of all the source code contained in your directory. Vim will pick this up automatically and allow you to jump to tags from within your editor. This is a must-have feature. And a clean target that will remove any auto-generated files.

Another nice thing about this makefile is that it can be referenced within the kernel tree itself, or it can be used to build an out-of-tree module. For this exercise, we will build an out-of-tree module, meaning we’re not building our code as part of a larger kernel compile. This code will be built against the headers for the running kernel and produce just a kernel module that can be inserted into the running kernel.

We create a simple test module in my-module.c

#include <linux/module.h>

#define AUTHOR      "My Name <me@some-host.com>"
#define LICENSE     "GPL"

static int __init my_module_init(void)
{
        printk(KERN_INFO "ZOMG I'M ALIVE ! ! !\n");
        return 0;
}

static void __exit my_module_exit(void)
{
        printk(KERN_INFO "We've been hit captain, abandon ship!\n");
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_AUTHOR(AUTHOR);
MODULE_LICENSE(LICENSE);

You’ll likely need to fix the single and double quotes if you’re pasting into your editor from the browser.

Building a kernel module from this should be as simple as

$ make

If this fails, make sure you have the headers for the currently running kernel installed on your system.

Upon successful compilation, load the resulting module into your kernel

$ sudo insmod ./my-module.ko

If the prompt returns with no error message, you’re good to go. The kernel logs informational messages to a ring buffer that can be accessed with dmesg. You should see your init string contained in the output of that command.

The module is removed from the kernel with

$ sudo rmmod my-module

The output of dmesg should now contain your exit string.

It’s important to note that rmmod does not guarantee you return to the state before the module was loaded. The code you compiled and loaded is executed within the context of the kernel and not within any sort of sandboxed environment. This means that any public data within the kernel could have been modified. It is not always the case that a module cleans up after itself properly, so although rmmod succeeded, you may still require a reboot to return to a sane running state.

The above is essentially what you’ll read an any Linux development book. Nothing special, but it’s the most basic foundation so I feel it’s worth covering. Everything builds on this first step.