Kernel development can be relatively hard thing to get a grasp of. But if you already know C programming, kernel modules can be relatively easy way to get into the world of kernel internals. Kernel modules are small part of code within the kernel that can be loaded and unloaded upon demand. So these modules can be used for enhancing the kernel’s functionality without rebooting the system. Modules also help keeping the kernel’s image size small, since with modules you don’t need to build kernel all over again if you want to add some sort of functionality to your kernel image.

Prerequisites

Getting started with kernel module development requires a few packages. I recently moved to OpenSUSE Tumbleweed, after hearing great things about it. I’m also running kernel version 4.15.7-1, while writing this.

To install essentials for kernel development in OpenSUSE, run:

$ sudo zypper in -t pattern devel_basis devel_C_C++ devel_kernel

This installs various packages needed for basic developing on many languages, including kernel development and C/C++.

Installing essentials in Debian based distributions:

$ sudo apt-get install build-essential kmod

These packages include essentials like GCC, Make, kmod etc. Kmod is the package that contains tools for managing kernel modules. Within the package are commands like modprobe, insmod, rmmod etc.

To see what modules are loaded in your current kernel run:

$ lsmod

If it prints a long list of different modules, your kmod should be working.

Before we can start building modules we need to install Linux header files. On OpenSUSE headers come from the patterns installed above. On Debian, you can install them with:

$ sudo apt-get install linux-headers-$(uname -r)

Ubuntu differs slightly from Debian in this field. To install Linux headers on Ubuntu run:

$ sudo apt-get install linux-headers-generic

After that you should be ready for making your own kernel module.

Note that I personally use OpenSUSE distributions, so I’m not entirely sure about how many other distributions do different things. So do your own research regarding to your own distributions and kernel version when it comes to packages for example.

Hello, World!

Before you start developing your own module it is recommended to read into the Linux kernel coding style.

Let’s start by making directory for the module:

$ mkdir module-hello
$ cd module-hello

Fire up your favorite editor and create a new C program. E.g.

$ emacs hello.c

And write the following:

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

int init_module(void)
{
    pr_info("Hello, World!\n");

    return 0;
}

void cleanup_module(void)
{
    pr_info("Goodbye, World!");
}

This is blogpost is made in Markdown, so it might format the code how it wants. If you end up copying this, remember to change indentation to tabs (8 characters), not spaces, again when in doubt look at the kernel documentation about coding style. Which can be found on link above or in /usr/src/linux/Documentation/process/coding-style.rst.

After you’ve written that, you need to make Makefile for it:

$ emacs Makefile
obj-m += hello.o

all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

Then you can compile your module with:

$ make

Output should be something like this:

make -C /lib/modules/4.15.7-1-default/build M=/home/topi/module-hello modules
make[1]: Entering directory '/usr/src/linux-4.15.7-1-obj/x86_64/default'
  CC [M]  /home/topi/module-hello/hello.o
  Building modules, stage 2.
  MODPOST 1 modules
WARNING: modpost: missing MODULE_LICENSE() in /home/topi/module-hello/hello.o
see include/linux/module.h for more information
  CC      /home/topi/module-hello/hello.mod.o
  LD [M]  /home/topi/module-hello/hello.ko
make[1]: Leaving directory '/usr/src/linux-4.15.7-1-obj/x86_64/default'

After compilation it makes various different files to your directory:

$ ls -l
total 364
-rw-r--r-- 1 topi users    182 Mar 15 17:47 hello.c
-rw-r--r-- 1 topi users 202992 Mar 15 17:55 hello.ko
-rw-r--r-- 1 topi users    853 Mar 15 17:55 hello.mod.c
-rw-r--r-- 1 topi users  73832 Mar 15 17:55 hello.mod.o
-rw-r--r-- 1 topi users  71136 Mar 15 17:55 hello.o
-rw-r--r-- 1 topi users    155 Mar 15 17:55 Makefile
-rw-r--r-- 1 topi users     40 Mar 15 17:55 modules.order
-rw-r--r-- 1 topi users      0 Mar 15 17:55 Module.symvers

Let’s focus on hello.ko, which is the compiled kernel object. You can info about the module with:

$ sudo modinfo hello.ko
filename:       /home/topi/module-hello/hello.ko
srcversion:     A053E6796BD5C397821E005
depends:        
retpoline:      Y
name:           hello
vermagic:       4.15.7-1-default SMP preempt mod_unload modversions

To load your module to kernel, run:

$ sudo insmod hello.ko

It shouldn’t return anything. You should see your module now on module listing:

$ sudo lsmod | grep hello
hello                  16384  0

And you should also see your kernel message on your system log:

$ sudo journalctl --since "1 hour ago" | grep kernel
Mar 15 18:34:43 tumble kernel: Hello, World!

You might have some entries before this depending what has happened in your kernel in the last hour. But hey! You made the kernel talk and also made your first module! Even though that is great, you might want to remove it. This can be done with:

$ sudo rmmod hello

And again you should see entry in your system log:

$ sudo journalctl --since "1 hour ago" | grep kernel
Mar 15 18:34:43 tumble kernel: Hello, World!
Mar 15 18:41:37 tumble kernel: Goodbye, World!

So that is very basics of creating and compiling modules and also installing and removing them. Kernel has slightly different language when its compared to regular C. So let’s take a more in-depth look into the code.

In-depth of Hello, World

You can see that I use slightly different headers when compared to regular C program. Linux kernel is completely independent C environment, so it doesn’t use standard C libraries. To read more about why it doesn’t use, read Can I use library functions in the kernel ? at KernelNewbies.

Program starts with regular #include like you see in any C program but they work the same way like #include <stdio.h> for example. So they bring more functionality to the program.

Here you can see that I use #include <linux/module.h> and #include <linux/printk.h>. module.h is required for all modules which implements the init_module() and cleanup_module(). printk.h implements printing macros, so the pr_info() in this example.

All the header files are located in /usr/src/linux/include/linux/ so it is recommended to read into these files.

Below the #include lines you can see to functions, init_module()and cleanup_module(). Kernel module must have at least two functions, one for initialization and one for cleanup. Initialization is called upon insmod and cleanup is called on rmmod.

Ever since kernel version 2.3.13 these functions are actually not needed and this is also the preferred way. You can name those function anything you want and then call these functions with module_init() and module_exit(). These macros are included in <linux/init.h>.

E.g.

#include <linux/module.h>
#include <linux/printk.h>
#include <linux/init.h>

static int __init hello_init(void)
{
    pr_info("Hello, World!");

    return 0;
}

static __exit hello_exit(void)
{
    pr_info("Goodbye, World!");
}

module_init(hello_init);
module_exit(hello_exit);

Here you can also see two macros __init and __exit. These are used for freeing up kernel memory. They don’t have any effect on loadable modules, but they are more important when we talk about built-in drivers.

Important thing to notice is also that the initialization function requires that it return non-negative value. If it returns negative value, it means that it has failed to load.

When it comes to Makefiles it is recommended to read real world examples. Linux source comes with a documentation for Makefiles, which can be found at /usr/src/linux/Documentation/makefiles.txt. Also read and see how they are written in some already written module in the source files.

Further Readings