Wambui Karuga

The Eudyptula Challenge: Task I

After my initial post introducing the Eudyptula Challenge, this is the first post of my solutions to the challenge. As with every programming tutorial or book, the first task is writing a very simple “hello world” kernel module.

Here was the description of the first task in the challenge:

  • Write a Linux kernel module, and stand-alone Makefile, that when loaded prints to the kernel debug log level, “Hello World!”. Be sure to make the module be able to be unloaded as well.
  • The Makefile should build the kernel module against the source for the currently running kernel, or, use an environment variable to specify what kernel tree to build it against.

So, right off the bat there are a few different concepts from the first task that are unique in kernel programming. I will mostly be using the Linux Device Drivers book as a reference and the code for my solutions is available on my GitHub – here is the specific solution for this task.

Loading and Unloading Kernel Modules.

The first part of the challenge description requires a kernel module that can be loaded and unloaded into the kernel. This means that the program (module) written can be added into and removed from the kernel at runtime, or during normal operations. Loading a new kernel module is done by the insmod utility while rmmod unloads the module from the kernel.

My “hello world” module uses two functions: hello_init to be called when the module is loaded and hello_exit to be called when the module is unloaded. Both functions are declared as static as they don’t really have any use outside of this specific file. This is common for initialization functions in kernel modules as the functions are not meant to be used or visible outside their specific files.

For the kernel to run the module code, the module must tell the kernel where to find its initialization function. This is done using the module_init macro which is used to mark the location of the module’s initialization function in its object code. An initialization function can also be marked using an __init decorator which tells the kernel that this function will only be used during initialization. Functions with the __init token are dropped after the module is loaded.

static int __init hello_init(void) {}

For unloading a module, a cleanup function is used to return any allocated resources back to the system. The cleanup function does not have a return value so it’s declared as void. The module_exit macro points the kernel to a module’s cleanup function – similar to the module_init macro and the initialization function. The __exit modifier can also be used with the module’s cleanup function to indicate that this function will only be used when the module is unloaded.

static void __exit hello_exit(void) {}

Without a cleanup function, the kernel will not allow the module to be unloaded. The macros used in this section are found in the linux/init.h header file.

For printing the challenge’s “Hello World!”, the kernel function printk is used. printk is similar to printf in C for printing formatted strings in the kernel. printk also uses various log levels that define the priority of the message. From the challenge, our message should have a priority of KERN_DEBUG, which will make is show up in our dmesg journal. The various printk priority levels can be found here.

Some other definitions used in kernel code include:

  • MODULE_AUTHOR which shows who wrote the code.
  • MODULE_DESCRIPTION which is a short description of what the module does.
  • MODULE_LICENSE which defines the license that the code uses. Using proprietary licenses with this macro usually “taints” the kernel.

These macros are found in the linux/module.h header file.

Compiling the Module.

The second part of the challenge requires writing a Makefile that will build the kernel against a kernel source.

A Makefile, in this context is a file with a set of rules passed to the GNU make utility that controls code compilation. Here is the Makefile for the “hello world” module.

When the make command is run, it runs the Makefile. The Makefile first checks if the KERNELRELEASE variable is set or not. If the variable is set, it indicates that the module is being built by the kernel build system, and can use the kernel’s build language. In this case, this module will only invoke the following line:

obj-m := hello.o

The kernel build system handles the rest and a module named hello.ko is produced from compilation.

If the KERNELRELEASE variable is not set, then the Makefile is being invoked from the command line and make will need to be shown where the kernel source is located.This is done using the KERNELDIR variable in the Makefile. In this case, the following Makefile directive is used to build the kernel.

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

This command starts by changing its directory to the one provided with the -C option where it finds the kernel’s top-level makefile. The M= option causes the kernel’s top-level Makefile to move back into your module source directory before trying to build the modules target. The modules target is the list of modules referred by the obj-m variable in the Makefile.

After writing both the module and Makefile, my “hello world” module is ready to be compiled:

➜ I git:(master) ✗ make
make -C /home/wambui/kernels/staging/ M=/home/wambui/kernels/eudyptula/I modules
make[1]: Entering directory '/home/wambui/kernels/staging'
CC [M] /home/wambui/kernels/eudyptula/I/hello.o
MODPOST 1 modules
CC [M] /home/wambui/kernels/eudyptula/I/hello.mod.o
LD [M] /home/wambui/kernels/eudyptula/I/hello.ko
make[1]: Leaving directory '/home/wambui/kernels/staging'
➜ I git:(master) ✗ ls
hello.c hello.c~ hello.ko hello.mod hello.mod.c hello.mod.o hello.o Makefile modules.order Module.symvers
➜ I git:(master) ✗

After successful compilation, the module can then be loaded and unloaded:

[wambui I]# ls
hello.c hello.c~ hello.ko hello.mod hello.mod.c hello.mod.o hello.o Makefile modules.order Module.symvers
[wambui I]# insmod ./hello.ko
Hello, World!
[wambui I]# #rmmod hello
I'm going...

And with that, the first challenge is done!

In general, there are three different types of kernel modules:

  • A character/char module that provides a device that can be accessed as a stream of bytes. These devices live in the /dev/ folder on the filesystem.
  • A block device module that implements a block device that can also be found in the /dev/ folder. Block devices are devices that can hold a filesystem and can perform input/output transfers of whole blocks of data.
  • A network device module that implements and controls a hardware or software network interface at the kernel level.

My “hello world” module does not fall into any of these categories, but it’s a great start on writing kernel modules! I’m looking forward to continuing with the challenge where I can build on top of the concepts learnt here.