The Kernel Newbie Corner: Your First Loadable Kernel Module


This is the first in a series of articles aimed at the beginning kernel programmer, designed to lead the kernel newbie through the basics of writing and compiling their first kernel module, and getting increasingly sophisticated from there. Everything that will happen in this space is meant to be fully hands-on, and you’re all welcome to play along.

This is ongoing content from the Linux Foundation training program. If you want more content please, consider signing up for one of these classes.

While I’ll try to keep this series distro-agnostic, I’ll admit up front that I have a tendency to do all my work under the latest version of Fedora, but translating to some other Linux distro should not be difficult.

And so, to work.

Getting Started

Every beginning kernel programmer has a dream–to write, compile and load their first kernel module. So if you follow along here, we’re going to do exactly that. In this opening article, we’re going to keep it simple, and leave the trickier stuff for later pieces. One step at a time, as they say.

Do You Need Root Privilege?

Ultimately, yes. There are ways to get around needing root privilege for things like setting up your kernel source tree and compiling your module, but there’s no way to avoid being superuser when you’re finally ready to load that module. So you either need the root password, or you need a friendly administrator–whichever is easier to get your hands on.

The Essential Bits and Pieces

There’s a shopping list of what you’ll need on your system, and here it is:

  • The version of your currently running kernel, available via uname -r, as in:

    $ uname -r

    (Actually, you don’t technically need that bit of information, so much as you just need to know what command to run to get it. You’ll see why shortly.)

  • The standard development packages such as gcc, binutils, and so on.

  • The package of module utilities containing insmod, rmmod, and so on (that would be module-init-tools under Fedora).

  • A kernel source tree to build against, and this might require a bit more explanation. So let’s do that.

Kernel Source Tree? What’s That All About?

This part’s important so let’s spend a couple minutes here. In order to compile a kernel module, you need at least part of a kernel source tree against which to compile. That’s because when you write your module, all of the preprocessor #include statements you use do not refer to your normal user space header files. Rather, they refer to the kernel space header files found in the kernel source tree so, one way or another, you have to have the relevant portion of some kernel tree available to build against.

While you can get fancy and download your own kernel source tree for this, a simpler and easier solution (for now) is to install the official kernel development package that matches your running kernel. That kernel development package normally installs, under /usr/src/kernels, just enough of the source tree to contain the necessary header files and build infrastructure, and little else. (In Fedora, this would be the kernel-devel package. Under other distros, your mileage may vary, as they say.)

Finally, once that package is installed, you’ll have to pass its directory location to your build step. You can either make a note of the actual directory name, or you can take advantage of the fact that it’s normally available under the /lib/modules directory, by way of a symlink. On this Fedora 11 system:

$ ls -l /lib/modules/`uname -r` 
... build ->

In other words, if that symlink exists, any time I need the location of the kernel source tree corresponding to the currently running kernel, I can always use the expression /lib/modules/`uname -r`/build, knowing it will keep up with any kernel upgrades. And that’s exactly what we’re going to do.

And now that we have our building blocks in place, on to writing our first module.

“Hello, Kernel!”

And without further ado, your first loadable module:

/* Module source file 'hi.c'. */  
#include <linux/module.h> // for all modules
#include <linux/init.h> // for entry/exit macros
#include <linux/kernel.h> // for printk priority macros
#include <asm/current.h> // process information, just for fun
#include <linux/sched.h> // for "struct task_struct"

static int hi(void)
printk(KERN_INFO "hi module being loaded.n");
printk(KERN_INFO "User space process is '%s'n", current->comm);
printk(KERN_INFO "User space PID is %in", current->pid);
return 0; // to show a successful load

static void bye(void)
printk(KERN_INFO "hi module being unloaded.n");

module_init(hi); // what's called upon loading
module_exit(bye); // what's called upon unloading

MODULE_AUTHOR("Robert P. J. Day");
MODULE_DESCRIPTION("You have to start somewhere.");

Some notes about the above:

  • Technically, I didn’t need to print anything upon module insertion or removal but, without some feedback, loading and unloading that module would be stultifyingly boring.

  • Always return zero from the init routine to signify a successful load.

  • Pick a valid license for your module, or you’ll end up “tainting” the kernel–something we’ll get into in the next article.

  • No, there is no comma after the log level in a printk statement. That’s a common mistake. Don’t make it.

So there’s our first module. Let’s build it.

The Makefile

Once again, let’s get right at the Makefile we’ll use to compile our module:

ifeq ($(KERNELRELEASE),)  

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

.PHONY: build clean

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

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

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


For now, don’t ask–just run it and see the results:

$ make build 
make -C /lib/modules/ M=/home/rpjday/lf/1
make[1]: Entering directory `/usr/src/kernels/'
Building with KERNELRELEASE =
CC [M] /home/rpjday/lf/1/hi.o
Building modules, stage 2.
Building with KERNELRELEASE =
MODPOST 1 modules
CC /home/rpjday/lf/1/hi.mod.o
LD [M] /home/rpjday/lf/1/hi.ko
make[1]: Leaving directory `/usr/src/kernels/'
$ ls -l hi.ko
-rw-rw-r--. 1 rpjday rpjday 126716 2009-06-24 19:18 hi.ko

And there it is–our loadable module hi.ko, ready to be loaded. But what was that Makefile all about?

To make a long story very short, that’s a two-part Makefile. When you run make to compile your module, the Makefile immediately realizes that you’re still in your source directory, at which point the make wanders off to the kernel source tree that you’ve identified, where it finds all of the necessary build infrastructure and kernel headers and so on, which it then uses to come back to compile your module.

We might come back some day and take a closer look at that. For now, take my word for it. If you got a hi.ko module out of it, you’re good. And we’re almost done here. But first …

Poking at that Module

If you want to examine your newly-built module while it’s just sitting there, no sweat–there’s the modinfo command:

$ modinfo hi.ko 
filename: hi.ko
description: You have to start somewhere.
license: Dual BSD/GPL
author: Robert P. J. Day
srcversion: 658D8123B9EE52CF16981C4
vermagic: SMP mod_unload

Which, mercifully, brings us to…

Loading and Unloading the Module

And assuming you managed to score root privilege, away we go:

# insmod hi.ko 
# lsmod
Module Size Used by
hi 1792 0 <--- oh, look!
... snip ...
# rmmod hi

But where did all your printk output go? Kernel programming rule one: you don’t normally interact with user space, so don’t expect to see print output coming back to your terminal. Instead, our output would have been directed to the standard syslog messages log file (in our case, /var/log/messages), so if we’d been keeping an eye on that file we would have seen:

Jun 24 19:33:01 localhost kernel: hi module being loaded.
Jun 24 19:33:01 localhost kernel: User space process is 'insmod'
Jun 24 19:33:01 localhost kernel: User space PID is 15359
Jun 24 19:33:03 localhost kernel: hi module being unloaded.

Piece of cake, no?

Cleaning Up the Mess and Starting Over

If you take one last look at that Makefile, you’ll notice that it defines the clean target for removing the results of your build, so toss it all if you want to start over:

$ make clean

It’s quite possible that that target doesn’t get rid of absolutely everything generated by the build, so it’s your job to figure out what’s missing and add it to the Makefile. Think of it as your first assignment.

In Conclusion

That’s as basic as it gets so test it and make sure it works on your system because, in future articles, it’s only going to get more complicated. Be prepared to keep up.

[Editor’s Note: A Spanish version of this article is now available.]

Robert P. J. Day is a Linux consultant and long-time corporate trainer who lives in Waterloo, Ontario. He provides assistance to the Linux Foundation’s Training Program. Robert can be reached at
This e-mail address is being protected from spambots. You need JavaScript enabled to view it
, and can be followed at