Learning Linux Kernel Module Programming

Navaneeth Krishnan
7 min readJan 5, 2018

--

I started doing Linux kernel module programming a couple of weeks ago. Although the progress has been painfully slow. I decided to document and blog whatever I have learnt. These are just my musings, almost all of the examples are from http://tldp.org/LDP/lkmpg/2.6/html/lkmpg.html

Here it goes ……..

Part 1 — About the Linux Kernel Module

The Linux kernel was built in a modular fashion, to allow users to customize the kernel in the manner they wanted. To give an example, imagine a company like Google who want to incorporate a different version of TCP congestion control algorithm on Linux,because they want to tweak it to the requirements of their own Data Center network. The option Google would have without the concept of Kernel modules would be to scan the entire Kernel code and find out the respective file or library which does congestion control and edit it. This is a time consuming process isn’t it?

Without kernel modules we would have to build monolithic kernels and add it directly to the kernel image. If you have worked on a raspberry pi you would notice that the Linux kernel is smaller in size than compared to the actual traditional Linux kernel image. The reason is only the core required modules are present and the remaining modules are unloaded. If the user wants it he can load it when he is using the system without needing to reboot it.

The following picture shows a part of the modules that are usually present on a standard Linux kernel operating system.

Some of the modules usually present in a traditional linux kernel

Now we could say modules are functional pieces of code that can be loaded or unloaded into the kernel on demand and without the need to reboot a system. A very good example of a kernel module is the device driver module. Lets go back to the previous example of the raspberry pi, which has obviously less memory capacity. So you want the footprint of the Linux kernel to be small. In that case we would remove unwanted device drivers like Bluetooth device driver for example or the printer driver. Because for raspberry pi to work out of the box we do not need these!!! But if the user wants it installed he can just install these modules at run time without the need for rebooting the system.

Part 2— My development environment

I used Ubuntu 16 running on a VMware workstation for my dev environment. Here are my settings!

Dev VM settings on Vmware Workstation

Once the VM is up and running you might want to run the following commands to get started with kernel programming.
1) sudo apt-get update
2) sudo apt-get install linux-headers-$(uname -r) build-essential
3)sudo apt-get upgrade
4) sudo apt-get install module-init-tools
5) reboot

4) reboot

Linux headers in command 2 basically installs the Linux kernel source code.

Part 3— Getting Modules Into Linux Kernel

The Linux kernel runs the kernel module daemon which gives the functionality of modprobe.
Modprobe is an intelligent way of loading modules into the kernel. When Modprobe is executed to load a module into the kernel it works in the following way
1) It checks the modprobe’s alias file to see if there is an alias name to the module name given to modprobe.
So the alias file can be system dependent. That is it can be present in different locations depending on which . The way I found it is I executed the command, strace module dummy.

File which contains the aliases of a module

For Ubuntu the file is present in /lib/modprobe.d/aliases.conf.

2) Modprobe then checks the modules.dep file present in

cat /lib/modules/$(uname -r)/modules.dep.

$(command name), is a way of executing a terminal command within a terminal command. Here the command being executed is uname -r which gives the Linux kernel version being used.

At this step modprobe checks if there are any dependencies to the given module being mentioned. The dependencies of a given module are mentioned in
cat /lib/modules/$(uname -r)/modules.dep
For example if you execute the command modprobe msdos, this module requires the fat32 kernel module to be loaded to run. Therefore modprobe msdos, will cause the following commands to be executed

insmod /lib/modules/$(uname -r)/kernel/fs/fat/fat.ko
insmod /lib/modules/$(uname -r)/kernel/fs/fat/msdos/msdos.ko

Now insmod is a dumb command and it will require you to mention the complete path name of the module. Whereas modprobe does not require you to have the complete path name of the module.
The modprobe sees if the module is present in /lib/modules/$(uname -r) and sees if there are any dependencies for this module and then loads the module as well as its dependencies.

Part 4— Hello World

We will be writing an Hello World kernel module.

Note — I think this is the right place to mention about modversioning. If you place a module in a Linux kernel and compile it, then you cannot use the module for another Linux kernel unless the other Linux kernel is compiled with CONFIG_MODVERSIONS option turned on .

I think its a good practice to create a separate directory for every kernel module. So lets create a separate directory called hello_world.

In the hello_world directory create a file called hello-1.c and type in the following code.

#include<linux/module.h>
#include<linux/kernel.h>
int init_module(void)
{
printk(KERN_INFO “Hello World 1 \n”);
return 0;
}
void cleanup_module(void)
{
printk(KERN_INFO “Goodbye World\n”);
}

The #include<linux/module.h> needs to be included for every kernel module.
The #include<linux/kernel.h> is required for the macro expansion of printk

Every Kernel module needs to have at least 2 functions
1) init_module function — This function is executed as soon as the kernel module is loaded into the kernel. A positive value is returned on every successful load of the kernel module

2)cleanup_module function — This function is executed as the kernel module is removed from the kernel.

Here the init_module and cleanup_module function prints to the console log.

Since its a kernel module, normal printf functions cannot be used however we can use printk to print warnings,errors and panics to the console. That is they can be seen by executing the command
dmesg

or

cat /var/log/messages

There are 8 priorities to the printk, mentioned in the file

/lib/modules/$(uname -r)/build/include/linux/kern_levels.h

Changing the priorities indicates the priority of the message to the kernel and also to where it is printed depending on the current console log level.

Each Kernel module should have a make file. Create a file called Makefile with the following code

obj-m += hello-1.oall:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

Note — The obj-m you see in the makefile indicates that the executable is built as a loadable kernel module. If you were to use obj-y, then executable is to be built-in to the kernel. A Makefile is only responsible for building objects in its own directory. Files in subdirectories should be taken care of by Makefiles in these subdirs. The Kbuild system will automatically invoke make recursively in subdirectories, provided you let it know of them by specifying obj-m or obj-y

Now we are ready to compile the module. Compile it by typing make on the terminal. This generates a .ko file.

Output when you type make command

Insert the module into the kernel by
insmod hello-1.ko
Now if everything was done right you should see the following output when you type dmesg on the terminal

Output for dmesg when module is inserted into kernel

Lets remove the module by running rmmod hello1.ko. Once again on running dmesg we see the following output

Output for dmesg when module is removed from kernel

Part 5— Hello World 2

In this section we will write the hello_world with custom init and exit function names by making use of the module_init() and module_exit() macros defined in
/lib/modules/$(uname -r)/build/include/linux/init.h

Creating a new directory called hello_world_2. We write a C file hello-2.c with the following code

#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/init.h>
static int hello_2_init(void)
{
printk(KERN_INFO "Hello World \n");
return 0;
}
static void hello_2_exit(void)
{
printk(KERN_INFO "GoodBye World \n");
}
module_init(hello_2_init);
module_exit(hello_2_exit);

Here in this code we register hello_2_init and hello_2_exit as the module’s init and module’s cleanup functions respectively. Therefore module_init and module_exit Macros allow us to define our own custom init and exit function names.
Note- In case, some of you were wondering what static means in a C function. It makes the function local to the particular file and cannot be used outside that file.

The Makefile for this would be

obj-m+=hello-2.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

Do a make of this module, insert and remove it. You should have the respective messages printed in your dmesg

Output of dmesg for hello-2.ko

--

--

Navaneeth Krishnan

Linux and Computer Networks Enthusiast, follows the holy grail of operating systems!