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