Write Linux kernel module

Kernel

I am thinking to start a Linux Kernel series and write about modules, device drivers, proc and sysfs, unit test with kunit and so on; here is the first one, a simple World module.

1. World module

world.c

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

static char *whom = "World";

void world_print(char* greet)
{
	printk(KERN_INFO "%s, %s\n", greet, whom);
}
EXPORT_SYMBOL(world_print);

static int __init world_init(void)
{
	printk(KERN_INFO "Init, %s\n", whom);
	return 0;
}
module_init(world_init);

static void __exit world_exit(void)
{
	printk(KERN_INFO "Exit, %s\n", whom);
}
module_exit(world_exit);

MODULE_AUTHOR("Iulian Costan <kernel@costan.ro>");
MODULE_DESCRIPTION("World module that exports a symbol");
MODULE_LICENSE("Dual BSD/GPL");

Nothing crazy so far, a module is required to provide two functions init, exit and MODULE_LICENSE macro call. Then we have our main logic function world_print that is exported using EXPORT_SYMBOL macro and a char pointer variable.

1.1 Build World

Kernel Build System requires a special file called Kbuild that is used for compilation and contains all modules that need to be compiled.

Kbuild

obj-m += world.o
obj-m += hello.o

And of course the Makefile which is special as well, it must set KDIR variable that points to Linux kernel build tree.

Makefile

KDIR ?= /lib/modules/`uname -r`/build

default:
	$(MAKE) -C $(KDIR) M=$$PWD
clean:
	$(MAKE) -C $(KDIR) M=$$PWD clean

Compile

echo 'Compiling modules...'
make
Compiling module...
make -C /lib/modules/`uname -r`/build M=$PWD
make[1]: Entering directory '/usr/lib/modules/5.5.10-arch1-1/build'
  Building modules, stage 2.
  MODPOST 2 modules
make[1]: Leaving directory '/usr/lib/modules/5.5.10-arch1-1/build'

Once make-ing is done we end up with multiple files but most of them are not important right now.

ls -l *world*
-rw-r--r-- 1 icostan users  554 Mar 26 12:14 world.c
-rw-r--r-- 1 icostan users 5608 Mar 26 12:14 world.ko
-rw-r--r-- 1 icostan users   50 Mar 26 12:14 world.mod
-rw-r--r-- 1 icostan users  560 Mar 26 12:06 world.mod.c
-rw-r--r-- 1 icostan users 2792 Mar 26 12:06 world.mod.o
-rw-r--r-- 1 icostan users 3984 Mar 26 12:14 world.o

We only care about .ko files and this is how our world module looks like.

modinfo world.ko
filename:       /home/icostan/Projects/blog/content/post/world.ko
description:    World module that exports a symbol
author:         Iulian Costan
license:        Dual BSD/GPL
srcversion:     B5F7CB29CC1BBCBDE62D173
depends:
retpoline:      Y
name:           world
vermagic:       5.5.10-arch1-1 SMP preempt mod_unload
parm:           whom:charp

With a tool called nm we can show all symbols imported/exported by this module, notice out little function world_print that is marked with T which means function is exported while lowercase t means not exported.

nm world.ko
0000000000000000 T cleanup_module
                 U __fentry__
0000000000000000 T init_module
0000000000000000 r __kstrtabns_world_print
0000000000000001 r __kstrtab_world_print
0000000000000000 r __ksymtab_world_print
0000000000000000 r _note_6
                 U param_ops_charp
0000000000000000 r __param_str_whom
0000000000000000 r __param_whom
                 U printk
0000000000000000 D __this_module
000000000000002f r __UNIQUE_ID_author23
0000000000000090 r __UNIQUE_ID_depends24
0000000000000000 r __UNIQUE_ID_description24
0000000000000044 r __UNIQUE_ID_license22
00000000000000a5 r __UNIQUE_ID_name22
0000000000000099 r __UNIQUE_ID_retpoline23
000000000000006d r __UNIQUE_ID_srcversion25
00000000000000b0 r __UNIQUE_ID_vermagic21
0000000000000059 r __UNIQUE_ID_whomtype21
0000000000000000 d whom
0000000000000000 t world_exit
0000000000000000 t world_init
0000000000000000 T world_print

1.2 Install World

echo 'Installing module...'
sudo insmod ./world.ko

Once our module was successfully installed,

journalctl -k | grep World
Mar 26 12:17:49 drakarys kernel: Init, World

we can grep /proc/kallsyms file and filter by our function world_print, which is exported, marked with T.

grep world_print /proc/kallsyms
0000000000000000 r __ksymtab_world_print	[world]
0000000000000000 r __kstrtab_world_print	[world]
0000000000000000 r __kstrtabns_world_print	[world]
0000000000000000 T world_print	[world]

For the curious minds, you can check how many symbols your kernel exports:

grep "T " /proc/kallsyms | wc -l
22574