FLUSP logo FLUSP - FLOSS at USP

Introduction to kernel build configuration and modules

Written on , last modified on

This tutorial shows how to make a simple Linux kernel module and how to create build configurations for new kernel features.

This tutorial was originally thought to be part of a set of tutorials tailored to aid newcomers to develop for the Linux kernel Industrial I/O subsystem. This is a continuation for the “Build the Linux kernel for ARM” tutorial.

Command Summary

If you did not read this tutorial yet, skip this section. This section was added as a summary for those that already went through this tutorial and just want to remember a specific command.

export ARCH=arm64; export CROSS_COMPILE=<cross_compiler_packagename_or_path>
make menuconfig
make -j$(nproc) Image.gz modules
make INSTALL_MOD_PATH=<path_to_rootfs> modules_install
make modules_prepare
make M=<path>

guestmount -w -a <disk_iname> -m <disk_partition> <local_directory>
guestunmount <local_directory>

scp -i ~/.ssh/rsa_iio_arm64_virt <file> root@<vm-ip>:~/

modinfo <module_name>
insmod <module_file.ko>
rmmod <module_name>
modprobe <module_name>
modprobe -r <module_name>
depmod -A

dmesg -w

Introduction to kernel build configuration and modules

This part shows how to build and test a simple kernel module and explores the Linux kernel build configuration further by explaining how to use menuconfig to enable kernel features and how to create your own build configuration for a simple example module.

Summary of the parts of this tutorial:

  1. Creating a simple example module
  2. Creating Linux kernel configuration symbols
  3. Configuring the Linux kernel build with menuconfig
  4. Installing Linux kernel modules
  5. Dependencies between kernel features

1) Creating a simple example module

From the root of the Linux kernel source code, create the file drivers/misc/simple_mod.c and add the code for the simple mod in there.

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

static int __init simple_mod_init(void)
{
	pr_info("Hello world\n");
	return 0;
}

static void __exit simple_mod_exit(void)
{
	pr_info("Goodbye world\n");
}

module_init(simple_mod_init);
module_exit(simple_mod_exit);

MODULE_LICENSE("GPL");

2) Creating Linux kernel configuration symbols

Now, let’s create a Kconfig configuration symbol for our simple module and add the associated Kbuild configuration option build it. When adding new Kconfig symbols we usually write them in the Kconfig file that stands in the same directory of the thing we want to build. The simple_mod.c module is under drivers/msic/ so we will add a configuration symbol for that in drivers/misc/Kconfig.

When adding entries to kbuild Kconfig and Makefiles we also follow the convention of keeping the entries in alphabetical order. The convention is not enforced by the build system so out of order entries will not prevent us from building the kernel. Nevertheless, keeping entries in order definitely helps developers find build configurations when looking for them. Also, the Linux kernel community will ask code submitters to keep things organized when upstreaming new configuration symbols. Let’s keep the good practices.

config SIMPLE_MOD
	tristate "Simple example Linux kernel module"
	default	n
	help
	  This option enables a simple module that sais hello upon load and
	  bye on unloading.

The config keyword defines a new configuration symbol. Further, kbuild will generate a configuration option for that symbol which in turn will be stored as a configuration entry in the .config file. Configuration options will also show in kernel configuration tools such as menuconfig, nconfig, or during the compilation process. In particular, the SIMPLE_MOD configuration symbol has the following attributes:

Other common attributes for configuration symbols are:

Now we add the simple_mod module to the list of build objects in drivers/misc/Makefile.

obj-$(CONFIG_SIMPLE_MOD)		+= simple_mod.o

That’s all we need for enabling the configuration of our simple_mod with kbuild.

3) Configuring the Linux kernel build with menuconfig

Run menuconfig and enable our example module.

cd $IIO_DIR
export ARCH=arm64; export CROSS_COMPILE=aarch64-linux-gnu-
make menuconfig

Type forward slash (/) to search by symbol name. In the search screen, type simple_mod then enter. The description of the simple_mod will appear. Type 1 to go to the configuration option. With selection over the simple_mod option, type m to enable it as a module. Save the configuration and exit menuconfig.

Enabling simple_mod with menuconfig
Figure 1. Enabling simple_mod with menuconfig

Build image and modules again.

time make O=../iio_workshop_build/ KCONFIG_CONFIG=../iio_workshop_build/.config Image.gz modules -j4

Install new kernel modules. No need to copy or install the kernel image since virt will pick the generated image file at arch/arm/boot/Image.

cd $VM_DIR
# Be sure the VM is shut down
guestmount -w -a iio_arm64.qcow2 -m /dev/sda2 mountpoint_arm64/
cd $IIO_TREE
make INSTALL_MOD_PATH=$VM_DIR/mountpoint_arm64/ modules_install
guestunmount mountpoint_arm64

Boot the virtual machine with virsh.

sudo virsh start iio-arm64
sudo virsh net-dhcp-leases default
ssh -i ~/.ssh/rsa_iio_arm64_virt root@<vms-ip-address-here>

Verify the kernel version.

# Run these inside the virtual machine
uname -a
cat /proc/version

4) Installing Linux kernel modules

Run modinfo which shows main info related to a kernel module. When known, modinfo will show the module file name, module author, module description, license, ailas, dependencies, signature, and signer.

# Run these inside the virtual machine
modinfo simple_mod

List currently loaded kernel modules.

# Run these inside the virtual machine
lsmod
root@localhost:~# lsmod
Module                  Size  Used by
crct10dif_ce           12288  1
cfg80211              409600  0
rfkill                 28672  2 cfg80211
drm                   577536  0
dm_mod                131072  0
ip_tables              28672  0
x_tables               40960  1 ip_tables

Notice our simple_mod is not loaded. Let’s take care of it. There are two ways of loading a Linux kernel module: insmod and modprobe.

insmod takes a path to a module file (.ko) and loads that into the running kernel. The kernel object file doesn’t really need to have been installed as we did with modules_install. rmmod unloads the module. Load our example module with insmod then run dmesg to see kernel log messages.

# Run these inside the virtual machine
insmod /lib/modules/$(uname -r)/kernel/drivers/misc/simple_mod.ko
dmesg | tail
<snipped>
[ 3962.547283] Hello world

Remove the module with rmmod.

# Run these inside the virtual machine
rmmod simple_mod
dmesg | tail
<snipped>
[ 3973.986089] Goodbye world

We can do the same with modprobe.

# Run these inside the virtual machine
modprobe simple_mod
dmesg | tail
modprobe -r simple_mod
dmesg | tail

Instead of a module file, modprobe takes the module name as argument. For that to work, the module has to be installed within the kernel and module tracking files (such as modules.dep) must contain references to the requested module. The advantage of having that is that modprobe will look for module dependencies and (if any) properly load them before loading the requested module [1]. insmod does not check for any module dependencies.

5) Dependencies between kernel features

Let’s increment our example module to export a function that can be called by other modules.

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

void simple_mod_func(void)
{
	pr_info("Called %s, %s function\n", KBUILD_MODNAME, __func__);
}
EXPORT_SYMBOL_NS_GPL(simple_mod_func, IIO_WORKSHOP_SIMPLE_MOD);

static int __init simple_mod_init(void)
{
	pr_info("Hello from %s module\n", KBUILD_MODNAME);
	return 0;
}

static void __exit simple_mod_exit(void)
{
	pr_info("Goodbye from %s\n", KBUILD_MODNAME);
}

module_init(simple_mod_init);
module_exit(simple_mod_exit);

MODULE_LICENSE("GPL");

Rebuild the example module and copy it to the virtual machine.

cd $IIO_TREE
export ARCH=arm64; export CROSS_COMPILE=aarch64-linux-gnu-
make M=drivers/misc/
scp -i ~/.ssh/rsa_iio_arm64_virt drivers/misc/simple_mod.ko root@<vms-ip-address-here>:~/

The M= option specify a directory for external module build. With that, we can only rebuild the modules of a child directory such as drivers/misc. Inside the virtual machine, test the new simple_mod version. No need to reboot.

# Run these inside the virtual machine
# @VM
cp simple_mod.ko /lib/modules/`uname -r`/kernel/drivers/misc/
depmod -A
modprobe simple_mod
modprobe -r simple_mod
dmesg | tail

Now, let’s add a module to call the exported simple_mod function.

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

extern void simple_mod_func(void);

static int __init simple_mod_part_init(void)
{
	pr_info("Hello from %s module\n", KBUILD_MODNAME);
	simple_mod_func();
	return 0;
}

static void __exit simple_mod_part_exit(void)
{
	pr_info("Goodbye from %s\n", KBUILD_MODNAME);
}

module_init(simple_mod_part_init);
module_exit(simple_mod_part_exit);

MODULE_LICENSE("GPL");
MODULE_IMPORT_NS(IIO_WORKSHOP_SIMPLE_MOD);

Also add entries to drivers/misc/Kconfig and drivers/misc/Makefile as we did for simple_mod.

config SIMPLE_MOD_PART
	tristate "Simple Test Partner Module"
	depends on SIMPLE_MOD
	help
	   Enable this configuration option to enable the simple test partern
	   module.
obj-$(CONFIG_SIMPLE_MOD_PART) += simple_mod_part.o

Run menuconfig again to enable simple_mod_part to build as a module.

make menuconfig

You will notice though, if you try to build the modules with make M=drivers/misc/ that it will build simple_mod but won’t build simple_mod_part. To get simple_mod_part built and setup as we have simple_mod, run the modules_prepare rule. Alternatively, we can rebuild the kernel and install the modules again. Let’s go for the first option which is faster.

cd $IIO_TREE
make modules_prepare
make M=drivers/misc/
scp -i ~/.ssh/rsa_iio_arm64_virt drivers/misc/simple_mod_part.ko root@192.168.122.209:~/

Load simple_mod_part and check out the output in kernel logs.

# Run these inside the virtual machine
cp simple_mod_part.ko /lib/modules/`uname -r`/kernel/drivers/misc/
depmod -A
modinfo simple_mod_part
modprobe simple_mod_part
modprobe -r simple_mod_part
dmesg | tail
[  119.775025] Hello from simple_mod module
[  119.777102] Hello from simple_mod_part module
[  119.777290] Called simple_mod, simple_mod_func function
[  128.551064] Goodbye from simple_mod_part
[  128.563960] Goodbye from simple_mod

Note you don’t have to explicitly ask to load simple_mod if using modprobe. To summarize, when adding new modules we need to prepare them for inclusion or rebuild the whole kernel to get the modules correctly setup. After the modules have been setup, we can modify modules, build only those that were updated, copy them to the VM, update module dependancies with depmod, then test.

Proposed Exercices

  1. The .config file that comes with the arm64 VM is bloated with features built together with the kernel image (y config value) which is why even after make localmodconfig the .config file did not reduce significantly and the build took a lot of time. Run make allmodconfig to turn builtin configuration values into module values when possible. After that, boot the VM, regenerate the list of needed modules as described in Part 1, run make localmodconfig with new list of modules. Did it reduce .config size further? How much? Does the whole kernel build take less time with the new .config? Does the resulting kernel still boot?

  2. Sometimes developers lose track of what .config was used to generate a running kernel after messing arround for enough time. The in kernel configuration config (IKCONFIG) exports (imports?) the .config file used to build the kernel into the kernel image and make it later availabe as /proc/config.gz file. Enable IKCONFIG, rebuild the kernel and read your .config from /proc/config.gz within the VM.

  3. Customize the log messages for simple_mod and simple_mod_part. Add #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt at the top of the module source files. KBUILD_MODNAME will expand to the module source file name resulting in every message logged through pr_info() (and friends) being prepended by the module name. See include/linux/printk.h for documentation. Tip, add the above define before header inclusions to avoid build warnings.

Conclusion

History

  1. V1: Release

comments powered by Disqus