7. Writing a Kernel Module from scratch

First kernel module

Having taken the first three lessons in this course to lay the foundations for kernel programming, we’re now in a position to write, compile and load our first kernel module. And to be perfectly clear, what we’re going to be doing in this lesson is the absolute bare minimum to do just that.

What I mean by that is that we’re going to write the simplest, most unproductive, content-free module imaginable. It will have no purpose and it will do nothing when it’s loaded, but that’s fine because, once we can do that, everything that follows in the rest of this course is nothing more than adding more and more features to our modules. Once you can load and unload a kernel module that you wrote yourself, everything else from that point on is just learning what else you can add to it. And there will be a lot to add. Oh, yes.

A fairly complete syllabus will be posted shortly once I finalize the list of topics.

And on that note, let’s get to our first kernel module.

How are we going to do this?

I’m going to change the way I normally explain how to do this. Rather than slowly and methodically build up to what it takes to run that first module (as I normally do), I’m simply going to throw out the code and tell you what to do in terms of running commands. with no explanation whatsoever. None. Just follow along, type what I tell you to, and we’ll verify that you can load and unload your first module, after which we’ll go back and I’ll explain in detail what just happened.

What software do you need to install?

There’s a good chance that you already have the necessary software packages on your system but let’s make sure. At a bare minimum, you’ll want the standard development tools such as gcc, make, assemblers, linkers and the like. If you are (as I am) running Ubuntu 10.04, you can get all of that with:

$ sudo apt-get install build-essential

For other distros, I’ll come up with the list of packages you’ll want later but, as I said, you might already have everything you need. If you don’t, the build process will tell you quickly enough, and you can just ask for help in the comments section.

Oh, and one more thing. Most of what follows can be done without root privilege so you should be creating these source files and building them in your regular user home directory somewhere. The only time you’ll need root privilege is to do the actual module loading and unloading.

The “Hello World” module

Pick a location to start writing modules somewhere in your home directory — let’s say a new directory named “helloworld”, where we’ll write our first module named (coincidentally) “helloworld”. (The directory name doesn’t have to match the module we’re going to write — it’s just easier to keep track of things that way.) And now, just do exactly what follows, which should simply work.

In that new directory, create the module source file helloworld.c containing something resembling:

/* Module source file 'helloworld.c'. */
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>

static int hi(void)
{
     printk(KERN_INFO "Helloworld module being loaded.\n");
     return 0;
}

static void bye(void)
{
     printk(KERN_INFO "Helloworld module being unloaded.\n");
}

module_init(hi);
module_exit(bye);

MODULE_AUTHOR("Myname, https://linuxinsomnia.wordpress.com");
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("Doing a whole lot of nothing.");

In that same directory, create a Makefile containing:

ifeq ($(KERNELRELEASE),)

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

.PHONY: build clean

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

clean:
        rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c
else

$(info Building with KERNELRELEASE = ${KERNELRELEASE})
obj-m :=    helloworld.o

endif

And note carefully that Makefiles require that indentation you see above to be a Tab character, not a bunch of spaces.

Once you’ve created those two files, compile your kernel module by running:

$ make

If you see output resembling:

$ make
make -C /lib/modules/2.6.32-22-generic/build
  M=/home/sandipan/Programs/kernel_modules/helloworld   modules
make[1]: Entering directory `/usr/src/linux-headers-2.6.32-22-generic'
Building with KERNELRELEASE = 2.6.32-22-generic
  CC [M]  /home/sandipan/Programs/kernel_modules/helloworld.o
  Building modules, stage 2.
Building with KERNELRELEASE = 2.6.32-22-generic
  MODPOST 1 modules
  CC      /home/sandipan/Programs/kernel_modules/helloworld.mod.o
  LD [M]  /home/sandipan/Programs/kernel_modules/helloworld.ko
make[1]: Leaving directory `/usr/src/linux-headers-2.6.32-22-generic'
$

and you have a bunch of new files in your directory including one named helloworld.ko, congratulations — you’ve just compiled your first kernel module. That wasn’t so hard, was it?

The loading and unloading of it all

At this point, you’re ready to load your kernel module, First, you can poke at that generated .ko file to see its internal format and — based on your architecture — you should see something like this:

$ file helloworld.ko
helloworld.ko: ELF 64-bit LSB relocatable, x86-64, \
  version 1 (SYSV), not stripped
$

at which point (to make a long story short), you can load, verify and unload your new module thusly:

$ sudo insmod helloworld.ko   [load it]
$ lsmod                   [verify the loading]
...
helloworld         977  0     <--- Voila! :-)
...
$ sudo rmmod helloworld       [unload it]
$

Piece of cake, no?

And now that you’ve followed my instructions blindly and to the letter and all of that worked perfectly, well, this is where the fun starts — explaining it all.

Kernel programming versus userspace programming

The most important thing you need to understand at this point is the fundamental difference between programming in userspace and programming for the kernel, because when you’re programming for the kernel, you’re working with a completely different set of rules.

Readers who are used to standard userspace programming know that writing a simple C program involves including some header files, then compiling their program and getting a resulting executable which, when it runs, will typically link dynamically against the routines in the standard C library. When you’re programming for the kernel, however, while the general process is similar, the location of all of the above changes.

Your new module is not going to run in userspace. It will have nothing to do with the standard C library header files, and it will not link against the C library. When you include header files in your module source code, those header files will come from the kernel header files. And when your module is loaded and starts to run, it will be running entirely in kernel space and will have to link against the kernel library routines. In short, you will be dealing with an entirely different development and execution environment, provided by the kernel.

And where is all of this content that you’re going to need? If you still have your kernel source tree, it’s easy to find. At the top level of the source tree, you can see an include/ directory — that’s a major source of kernel header files.

In addition, the kernel has to supply all of its supporting library routines — even things as simple as string manipulation functions — and you can see the kernel library routines in the top-level lib/ directory. In short, and once again, everything you need to write, compile and run your own loadable modules has to be supplied by the kernel. All that experience you have in userspace C programming? Just put that away for the time being.

The only thing that’s common is that you’ll still be using the standard development tools, such as make and gcc, but your working environment will be totally new.

The module build environment

Now that you understand that the simple act of compiling your new module requires a wholly new build environment (with new header files and new library routines and so on), how do you get that environment? It should be obvious that that build environment is supplied by the kernel source tree — specifically, all of the Makefiles scattered throughout the tree that control what gets built, and when, and where to search for headers files, and where to find library routines and all the rest of it. But your module isn’t in a kernel source tree. So how does that help you? Easy.

Without getting into tedious detail, take a look at your module Makefile again. Keeping a long story short, what that Makefile does is note the source file that you want to compile (helloworld.c, remember?), at which point it follows a reference to where it can find a kernel source tree that contains everything required to build a module, at which point you’ve supplied enough information for the kernel build infrastructure to come back to your module and compile it properly. It’s really quite elegant.

So where’s that kernel source tree you need?

Let me repeat what I wrote above — to compile even the simplest kernel module, you need a kernel source tree somewhere against which to compile it. Without one, there’s nothing you can do, so you have to track one down somewhere. But wait … there’s a bit more to it than that.

In fact, you don’t even need the entire source tree. If you think about it, compiling your trivial module won’t require much of the kernel tree’s driver source. All you really need from the kernel tree are the header files, the library routines and the internal build infrastructure (that is, the collection of Makefiles and scripts that support building). All the rest is irrelevant. And what’s so nice about that is that most Linux distros have actually packaged precisely just that part of the source tree you need, so you need install just that package to now have the required portion of the kernel source for building your own modules.

In fact, there’s a good chance that that package is already on your system. On Ubuntu, the last time I looked, the package name would be linux-headers-2.6.32-22. On Fedora, it’s probably kernel-headers. Regardless of the name, you can check quickly enough since the normal installation location for that partial source tree is under the /usr/src directory, so on my Ubuntu 10.04 system, I can see the following:

$ ls /usr/src
linux-headers-2.6.32-21          linux-headers-2.6.32-22
linux-headers-2.6.32-21-generic  linux-headers-2.6.32-22-generic
$

which looks good. In fact, all you need to verify is that you have the kernel headers that correspond to the kernel you’re building your modules for. And here’s the best part.

You don’t even need to figure out where those headers live, because the convention is that, for each bootable kernel, you might recall that the modules directory is always addressable as /lib/modules/$(uname -r) and, furthermore, there should be a symbolic link under each of those kernel-specific directories that points to the corresponding kernel headers, as in:

$ uname -r
2.6.32-22-generic
$ ls -l /lib/modules/$(uname -r)
...
lrwxrwxrwx ... build -> /usr/src/linux-headers-2.6.32-22-generic
...
$

In other words (and, really, we’re almost done here), regardless of what kernel you’re running, you should expect that you can always refer to the kernel source tree against which to build your modules with the expression /lib/modules/$(uname -r)/build, which suddenly explains this line from your module’s Makefile:

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

(The addition of the word “shell” is simply because it’s inside a Makefile.)

Comments are closed.
%d bloggers like this: