Compile a Linux Kernel Module - Eudyptula Task 1

Fri, Jul 5, 2019

How to get your first LKM linux kernel module loaded into your kernel

Now is time to complete the first Eudyptula Challenge Task: we need to create a kernel module that can be loaded and unloaded and prints “hello world”. It shouldn’t be that hard…

I like when I learn something new to really understand everything I’m doing and not just how to get the exercise done. Why? Because the exercises are just an excuse or better… a guideline to facilitate the learning process.

My first question is… what is a kernel module? I mean, everybody knows what it is, but what I’m asking is:

I really don’t know the answer to those questions. So I googled it, and I found an amazing resource in, it’s called The Linux Kernel Module Programming Guide

"Everyone's system is different and everyone has their own groove. Getting your first `hello world` program to compile and load correctly can sometimes be a trick. Rest assured, after you get over the initial hurdle of doing it for the first time, it will be smooth sailing thereafter" - LKMPG Book

And it was true, getting my first kernel module loading in QEMU wasn’t an easy task. But I will try to make it easier to you.

LKM Compilation

My background on C/C++ came from coding video games with Irrlicht and Ogre3D, so I used a combination of CMake and Make to do some cross platform games. So, my initial approach was to use Autotools to compile my first LKM. I thought that was the right Linux way of doing things. I was extremely wrong. Not only a simple Makefile is enough, but it’s also the recommended approach. Please, before you attempt to compile your first LKM module, you need to read this files first:

Read them! After you read them, everything is really easy. My Makefile is the following:

ifeq ($(KERNEL),)
KERNEL := /lib/modules/$(shell uname -r)/build
obj-m += task1.o
task1-objs := main.o other.o

	ccache make -C ${KERNEL} M=$(PWD) modules

	rm -f _kernel_sources
	ln -s ${KERNEL} _kernel_sources
	gtags --incremental --verbose

	make -C ${KERNEL} M=$(PWD) clean

The ifeq ($(KERNEL),) checks the KERNEL argument is given to make. If the argument is absent, then it uses the default kernel path /lib/modules/$(shell uname -r)/build. uname -r prints the kernel release. I made that so that anybody could just call make for a default build, or make KERNEL=<custom_path> for a custom kernel location.

obj-m += <lkm_name>.o holds the final name of your module. In the example the final LKM name will be task1.ko.

<lkm_name>-objs := <source_name>.o ... are the different names of your C source code files.

Then for the compilation itself (all target), we call make changing the directory -C to ${KERNEL} to actually cal the linux kernel’s Makefile indicating that we want to build a LKM located M= in $(PWD). The Ccache you see before the make is to speed the compilation, but It’s not necessary in this case, is just to let you know that Ccache exists.

Other interesting aspect of this Makefile is the the tags target. There I create a symbolic link to the kernel location and then I run gtags to generate tags for both, my project and the linux kernel in one GTAGS. In practice I was able to have code navigation using two GTAGS, one that is generated in this way in my project, and the other GTAGS in the kernel source directory. If you know a better way please tell me, because I tried with doing it “the right way” with multiple etags(TAGS) and nothing worked very well.

$ cd <kernel_path>
$ make
$ make modules_prepare
$ cd <my_lkm_path>
$ make KERNEL=<kernel_path>

As you see in the last code you need to have an already built kernel before you can compile your module. And just in case you may want to run make modules_prepare to check if everything is fine to build modules.

C Module

#include <linux/module.h>
#include <linux/kernel.h>

int init_module(void)
	printk(KERN_INFO "Hello world\n");
	return 0;

void cleanup_module()
	printk(KERN_INFO "Goodbye world\n");

As you can see the code itself is extremely simple, almost without the need of any explanation. And that is awesome!

We need at least one function that will be called when the module is loaded.

int init_module(void);

Optionally we can add an “unload” function to make our module unloadable.

void cleanup_module(void);

Finally, you should always include <linux/module.h>.

Module Loading

To get our module loaded we need to understand these commands:

The once you read the man of each one of those commands you should know how to load a module. Basically you can call directly lsmod and you are done.

I recommend you to load kernel modules inside QEMU after you know how to create your own Initramfs. If you go for the virtualization route, it will take you much more time, but after you learn how to it, your development cycles will be much faster. Also, you isolate your own kernel from faulty modules.