Lesson 7: Tidying up some loose ends, and the end of the free content.

Printer-friendly versionPrinter-friendly version

What's this all about?

With this lesson, we'll have reached the end of the free course content, and I'll have more to say about that at the end of the lesson, but now that you've managed to write and load your first Linux kernel module, it's time to go back and fill in a few details about what just happened in that last lesson, so that you understand not only what happened, but why it happened.

There will still be a great deal of increasing detail as this course progresses, but we can at least shed some light on a few more details of loadable modules.

Let's talk about that source file

Let's re-examine your module source file, just so you appreciate what each line represents and why it's there. Here's the original source for your mod1.c module:

/* 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.");

So let's start at the top and work our way down.

As with standard user space C programming, you're almost always going to need to include some header files in your module. But remember that those header files don't come from user space. Rather, they're pulled in from the include/ directory in the kernel source tree that you're building against. Quite simply, you'll never include regular user space headers in your kernel space source code. And those three header files you see above are almost always the bare minimum you'll need in any module you write.

Moving on to the two routines you see named hi() and bye(), these two routines are the entry and exit routines for your module; they're invoked when your module is loaded and unloaded, respectively.

If your loadable module represents an actual device driver, then it's normal for the entry routine to do everything necessary at driver load time, such as initialize the hardware, allocate memory, possibly clear registers and so on. Conversely, the exit routine would be responsible for doing exactly the opposite; shutting down the device, releasing allocated memory, that sort of thing.

In our case, our LKM did absolutely nothing of any value, but it was still necessary to define entry and exit routines, even if they do next to nothing. In addition, you're free to name those two routines whatever you want, as long as you use the following module_init() and module_exit() macros to identify them.

Finally, there are a number of macros available for you to add information to your module that you can examine later via the modinfo command. Most of them are optional, but the one you want to get right is the MODULE_LICENSE macro, for reasons we'll cover later in the course.

And that's pretty much it for the module source code. Now let's talk about the Makefile, which is going to be a little more interesting.

The intricacies of a module Makefile

Let's reproduce the Makefile from the previous lesson, then we can discuss it:

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

So what's going on here? To explain it, we have to back up just a bit.

As you can see, it's easy enough to write the source code for a loadable module. But once you write it, it has to be compiled against a kernel source tree. Your module code will be including header files, and will also probably call some kernel library routines, but none of that is available in your standard user space C library. All of that is supplied by a kernel source tree which is why the basic build process absolutely must specify such a source tree. if you don't have a kernel source tree lying around, you simply can't compile a loadable module, much less load it.

A kernel source tree? The entire thing?

For the sake of simplicity, we're going to cheat a little in this final free lesson because this process is covered in far more detail later in the course.

In order to compile a loadable module, you don't actually need a full kernel source tree. All you really need is that portion of the tree containing the header files and the build structure, but we can avoid those details for a simple reason.

We used Git to clone a kernel source tree, and we configured that tree for our system, and we compiled a new kernel, and we installed that kernel and rebooted to it, which means that if we want to compile loadable modules to run under this kernel, it makes perfect sense to compile against the very source tree that produced our current kernel. Again, at the risk of over-simplification, if we do that, it means everything matches and we shouldn't have any trouble. But we still have that Makefile to explain, so let's get to that.

Where is the module build structure?

One more time -- when you compile a LKM, it needs to be compiled using a very different build structure than you're used to in user space, since it's including different header files and calling different library routines. But that's not such a big deal since that entire build structure is, in fact, contained in the kernel source tree itself, and all you need to do is take advantage of it.

When you run make to build your module, your Makefile is actually invoked twice -- once directly, and once from the kernel tree build structure that is told to use its module-building infrastructure to come back to your directory and build your module for you.

Your Makefile is actually structured as an if-then-else construct, where the first part:

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

will be processed by your calling make, since it checks the value of a variable called KERNELRELEASE that initially has no value. Because of that, the first part of the Makefile is processed, which defines some local targets and, finally, using this rule:

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

actually runs make again, changing to the kernel source directory, and passing enough parameters so that the kernel build structure knows where to return to for the build.

The "else" part of the Makefile:

else

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

endif

does nothing more than defines what to compile when the make call returns to your directory.

In short, it's a two-pass Makefile, where you need to define only what the kernel build structure needs to know in order to come back to your directory and build your module for you. But there is one more detail worth knowing.

Picking a kernel source tree for the build

As I already mentioned, the simplest situation is that you're building your modules against the very kernel tree that was configured and used to build the kernel you're currently running. And how does your Makefile know where that tree is? Simple.

From an earlier lesson, you might remember that, when you install a new kernel, you should also install all of the modules that were built for it, and those modules normally end up in a subdirectory of /lib/modules. And in addition to all of the modules, what is also placed there is a symbolic link back to the kernel source tree used for that particular build.

My development system is currently running the 3.3.0-rday+ kernel and my source tree for the build is in the directory /home/rpjday/k/git, so I shouldn't be surprised to see the following:

$ ls -l /lib/modules/3.3.0-rday+/build
... build -> /home/rpjday/k/git
$

In short, in the middle of my module Makefile, as long as we keep things simple, we can always get the location of the corresponding kernel source tree for the running kernel with the expression /lib/modules/$(uname -r)/build, which is precisely what is being done above.

If you want to get trickier and build a module against a different source tree, you could always set the KERNELDIR variable in your shell environment or on the make command line itself, but we're not going down that road yet.

To sum up, at least for the time being, we're going to work with a single Git-cloned kernel source tree, and that will be the tree used to configure and build the running kernel. And as long as we stick with that scenario, we'll be able to build more and more sophisticated loadable modules that should all load and unload properly, and even do cool and interesting things.

End of the free content ... now what?

At this point, further course content will be available to subscribers only and, in a day or two, I'll have a detailed list of topics to be covered in the rest of this course and details on how to subscribe.

In the meantime, everything up to this point is, and will remain, publicly available. There is no expiration date for the free content, and you're welcome to share it with whoever you want and use it however you wish. Enjoy. And stop back in a couple days for details on the rest of the course.

And if you have questions, that's what the comments section is for.

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.