Lesson 6: Writing and running your first loadable kernel module (LKM)

Printer-friendly versionPrinter-friendly version

Finally, your first kernel module

Having taken the first several 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 loadable kernel module imaginable. It will have no actual 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.

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.

Regarding necessary software and root privilege

Based on an earlier lesson, you should already have the necessary software on your system for this lesson; if you don't, that will become obvious fairly quickly and it shouldn't be hard to figure out what's missing, and install it.

In addition, almost all 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. You shouldn't need to do anything else as root.

The mod1 module

Pick a location to start writing modules somewhere in your home directory -- let's say a new directory named "mod1", where we'll write our first module named (coincidentally) "mod1". (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 mod1.c containing something resembling:

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

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

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

module_init(hi);
module_exit(bye);

MODULE_AUTHOR("Robert P. J. Day, http://crashcourse.ca");
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 :=    mod1.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 simply running:

$ make

If you see output resembling:

make -C /lib/modules/3.3.0-rday+/build  M=/home/rpjday/courses/lkp2/mod1   modules  
make[1]: Entering directory `/home/rpjday/k/git'
Building with KERNELRELEASE = 3.3.0-rday+
  CC [M]  /home/rpjday/courses/lkp2/mod1/mod1.o
  Building modules, stage 2.
Building with KERNELRELEASE = 3.3.0-rday+
  MODPOST 1 modules
  CC      /home/rpjday/courses/lkp2/mod1/mod1.mod.o
  LD [M]  /home/rpjday/courses/lkp2/mod1/mod1.ko
make[1]: Leaving directory `/home/rpjday/k/git'
$

and you have a bunch of new files in your directory including one named mod1.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 mod1.ko
mod1.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 mod1.ko   [load it]
$ lsmod                   [verify the loading]
...
mod1         12481  0     <--- Voila! :-)
...
$ sudo rmmod mod1         [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 (mod1.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-[version]. 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 11.10 system, I can see the following:

$ ls -l /usr/src
total 48
drwxr-xr-x 24 root root 4096 2011-10-21 20:36 linux-headers-3.0.0-12
drwxr-xr-x  7 root root 4096 2011-10-21 20:36 linux-headers-3.0.0-12-generic
drwxr-xr-x 24 root root 4096 2011-11-25 21:21 linux-headers-3.0.0-13
drwxr-xr-x  7 root root 4096 2011-11-25 21:22 linux-headers-3.0.0-13-generic
drwxr-xr-x 24 root root 4096 2011-12-12 13:56 linux-headers-3.0.0-14
drwxr-xr-x  7 root root 4096 2011-12-12 13:56 linux-headers-3.0.0-14-generic
drwxr-xr-x 24 root root 4096 2012-01-24 10:40 linux-headers-3.0.0-15
drwxr-xr-x  7 root root 4096 2012-01-24 10:40 linux-headers-3.0.0-15-generic
drwxr-xr-x 24 root root 4096 2012-03-08 10:31 linux-headers-3.0.0-16
drwxr-xr-x  7 root root 4096 2012-03-08 10:31 linux-headers-3.0.0-16-generic
drwxr-xr-x 24 root root 4096 2012-03-13 07:58 linux-headers-3.0.0-17
drwxr-xr-x  7 root root 4096 2012-03-13 07:58 linux-headers-3.0.0-17-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
3.3.0-rday+
$ ls -l /lib/modules/$(uname -r)
...
lrwxrwxrwx 1 root root      18 2012-03-12 17:20 build -> /home/rpjday/k/git
...
$

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

Dude, where's my output?

Well, that's the last issue, isn't it? You've proved that you can write a module, compile it, then load it and unload it, but where did the output from those printk statements go?

Once again, remind yourself that this module is running, not in user space, but in kernel space, which means it's not simply going to send output to your screen. To make a long story short, output from kernel space printk calls is run through whatever syslog-related logging service you happen to be running, and will almost certainly end up in a log file somewhere under the /var/log directory.

On my Ubuntu system, the standard destination is the log file /var/log/kern.log, so before you load and unload that module, you can set up to list that log file in real time or "follow" mode to see the lines as they're being added. In one terminal screen, run:

$ sudo tail -f /var/log/kern.log

and once that's running, open a second terminal window and load and unload your new module to see something like this suddenly appear in the log file:

Mar 13 14:12:25 oneiric kernel: [18156.761436] mod1 module being loaded.
Mar 13 14:24:19 oneiric kernel: [18870.985249] mod1 module being unloaded.

And there's your output.

Let's recap

At this point, if you're completely new to Linux kernel programming, you've just done something fairly significant; you wrote, compiled and loaded your very first loadable kernel module.

There will be one (possibly two) more free lessons to tidy up a few loose ends, after which the content will switch to subscriber-only, and that will be explained in more detail when the time comes.

For now, just bask in the satisfaction of suddenly having become a Linux kernel programmer.

Comments

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <p> <br> <pre> <h1> <h2> <h3> <h4>
  • Lines and paragraphs break automatically.

More information about formatting options

By submitting this form, you accept the Mollom privacy policy.

We know

We're aware of the time and budget pressures at most companies, normally accompanied by the plaintive cry from management of, "Yes, I know we need training on that topic, but I just can't afford to send my entire team away for three (or four or five) days to get it!" And that's where we come in.

Choices!

The main focus at Crashcourse is to offer a choice of intense, 1-day, hands-on courses on specific topics in Linux and open source. And given that we already have the laptops for the delivery of that training, the idea is to show up early, set up a classroom, then spend the day teaching exactly the topic you're interested in. No travel time, and no wasted classroom time.

Customization

If we don't already have a course that addresses the topic you're interested in, drop us a note and we'll see what we can do -- our content providers can almost certainly put together a course that's precisely what you're after.

The difference

While there are a variety of sources for Linux and open source training, we at Crashcourse are taking a slightly different approach. Our philosophy is simple: exactly the training you want, and no wasted time or travel to get it.