Your first loadable kernel module. When loading into the kernel, it will generate a "Hello World" message. When unloading the kernel module, a "Bye" message will be generated.
- Kernel Source Tree
- Notes on Compiling a Module
- Writing Hello World Module
- Compiling Kernel Modules
- Loading and Unloading the Kernel Module
- A More Complicated Makefile
- Module Parameters
In order to compile a kernel module, you need at least part of a kernel source tree against which to compile. That’s because when you write your module, all of the libraries or helper routines you use do not refer to the normal user space files. Rather, they refer to the kernel space header files found in the kernel source tree. Therefore, you have to have the relevant portion of some kernel tree available to build against.
You can either download your own kernel source tree for this or install the official kernel development package that matches your running kernel. Kernel development package normally installs under /usr/src/kernel-version.
Example
sudo apt install linux-headers-$(uname -r)- Out of tree, when the code is outside the kernel source tree, in a different directory
- Not integrated into the kernel configuration/compilation process
- Needs to be built separately
- The driver cannot be built statically, only as a module
- Inside the kernel tree
- Well-integrated into the kernel configuration/compilation process
- The driver can be built statically or as a module
Create a new directory to place the Hello World kernel module.
mkdir hello_module
cd hello_moduleCreate a hello_mod.c file.
/* Module Source File 'hello_mod.c'. */
#include <linux/module.h>
// Module metadata
MODULE_AUTHOR("Zhou Qinren");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Hello World Module");
static int __init hello(void) {
// \n flushes the message immediately
printk(KERN_INFO "Hello World! Module being loaded.\n");
return 0; // To signify a successful load
}
static void __exit bye(void) {
// \n flushes the message immediately
printk(KERN_INFO "Bye! Module being unloaded.\n");
}
module_init(hello); // What is called upon loading this module
module_exit(bye); // What is called upon unloading this moduleThe __init macro tells the linker to place the code in a dedicated section into the kernel object file. This section is known in advance to the kernel, and freed when the module is loaded and the init function finished. This applies only to built-in drivers, not to loadable modules. The kernel will run the init function of the driver for the first time during its boot sequence. Since the driver cannot be unloaded, its init function will not be called again until the next reboot. There is no need to keep references on its init function anymore.
The __exit macro causes the omission of the function when the module is built into the kernel, and like __init , has no effect for loadable modules. Again, if you consider when the cleanup function runs, this makes perfect sense; built-in drivers do not need a cleanup function, while loadable modules do.
Kernel modules always begin with the function you specify with the module_init call. This is the entry function for modules; it tells the kernel what functionality the module provides and sets up the kernel to run the module’s functions when they are needed. Once it does this, entry function returns and the module does nothing until the kernel wants to do something with the code that the module provides.
All modules end by calling the function you specify with the module_exit call. This is the exit function for modules; it undoes whatever entry function did. It unregisters the functionality that the entry function registered.
Kernel programming rule one: you don’t normally interact with user space, so don’t expect to see printk() output coming back to your terminal.
The printk() function is the kernel’s version of the classic print function in C language, which prints to the kernel logs. You have the KERN_INFO macro for logging general information. You can also use macros like KERN_ERROR in case an error occurs, which will alter the output format.
Compiling a kernel module differs from compiling an user program. First, kernel space headers should be used. The module should not be linked to user space libraries. Additionally, the module must be compiled with the same options as the kernel in which we load the module. For these reasons, there is a standard compilation method Kbuild.
Goal definitions define the files to be built, any special compilation options, and any subdirectories to be entered recursively.
-
Built-in object goals -
obj-yThe Kbuild Makefile specifies object files for vmlinux in the
$(obj-y)lists. These lists depend on the kernel configuration. Kbuild compiles all the$(obj-y)files, which will be later linked into vmlinux. -
Loadable module goals -
obj-m$(obj-m)specifies object files which are built as loadable kernel modules.
Example
obj-$(CONFIG_FOO) += foo.o$(CONFIG_FOO) option indicates whether FOO feature should be included. It is set through .config file and evaluates to either y (for built-in) or m (for module). If CONFIG_FOO is neither y nor m, then the corresponding source file will not be compiled nor linked.
Contained in this file will be the name of the module(s) being built, along with the list of requisite source files. The file may be as simple as a single line:
obj-m := <module_name>.oThe Kbuild system will build <module_name>.o from <module_name>.c (auto-dependency generation), and after linking, will result in the kernel module <module_name>.ko.
When the module is built from multiple sources, an additional line is needed listing the files:
obj-m := <module_name>.o
<module_name>-y := <src1>.o <src2>.o ...If you want to build multiple separate kernel modules with one Makefile, you can list all of them in the obj-m variable.
obj-m += module1.o
obj-m += module2.o
obj-m += module3.oBelow is the Makefile for our module. From a technical point of view just the first line is really necessary, the all and clean targets were added for pure convenience (not used by Kbuild). Compile our module by running make.
# Makefile
obj-m := hello_mod.o
LINUX_KERNEL := $(shell uname -r)
LINUX_KERNEL_PATH := /usr/src/linux-headers-$(LINUX_KERNEL)
all:
$(MAKE) -C $(LINUX_KERNEL_PATH) M=$(PWD) modules
install:
$(MAKE) -C $(LINUX_KERNEL_PATH) M=$(PWD) modules_install
clean:
$(MAKE) -C $(LINUX_KERNEL_PATH) M=$(PWD) cleanThe command to build an external module is (the default target is modules when no target is specified, the output files will be generated in current directory):
make -C <path_to_kernel_src> M=$(PWD)The command to install the module just built is:
make -C <path_to_kernel_src> M=$(PWD) modules_installOptions:
-C $Kernel_DIR: The directory where the kernel source is located. make will actually change to the specified directory when executing and will change back when finished.M=$(PWD): Informs Kbuild that an external module is being built. The value given toMis the absolute path of the directory where the external module (Kbuild file) is located.
After compiling the kernel module, a file named hello_mod.ko was generated. This is a fully functional, compiled kernel module.
sudo insmod hello_mod.koYou can verify this by running lsmod, which lists all of the modules currently in the kernel. You can also run lsmod | grep hello_mod.
In the module source code, we print a "Hello World" message, to see that you can run sudo dmesg which prints the kernel’s logs to the screen and prettifies it a bit so that it’s more readable.
dmesg -T --follow-Tenables timestamps--followenables real-time (continuous) logs
dmesg -T --follow | tee log.txtWrites to both the standard output and a file named log.txt.
dmesg -cPrints the kernel message buffer and clears it.
sudo rmmod hello_modYou can verify this by running lsmod | grep hello_mod and sudo dmesg.
The example below demonstrates how to create a build file for the out-of-tree (external) module 8123.ko, which is built from the following files:
- 8123_if.c
- 8123_if.h
- 8123_pci.c
- 8123_bin.o_shipped (a binary blob)
# Makefile
ifneq ($(KERNELRELEASE),)
# Kbuild part of Makefile
obj-m := 8123.o
8123-y := 8123_if.o 8123_pci.o 8123_bin.o
else
# Normal Makefile
LINUX_KERNEL := $(shell uname -r)
LINUX_KERNEL_PATH := /usr/src/linux-headers-$(LINUX_KERNEL)
all:
$(MAKE) -C $(LINUX_KERNEL_PATH) M=$(PWD) modules
install:
$(MAKE) -C $(LINUX_KERNEL_PATH) M=$(PWD) modules_install
clean:
$(MAKE) -C $(LINUX_KERNEL_PATH) M=$(PWD) clean
# Module specific targets
createBin:
echo "X" > 8123_bin.o_shipped
endifAn external module always includes a wrapper that supports building the module using make with no arguments. This target is not used by Kbuild; it is only for convenience. Additional functionality, such as test targets, can be included but should be filtered out from Kbuild due to possible name clashes.
The check for KERNELRELEASE is used to separate the two parts of the Makefile. In the example, Kbuild will only see the first two assignments, whereas make will see everything except these two assignments. This is due to two passes made on the file:
- The first pass is by the
makeinstance run on the command line. At this point,KERNELRELEASEis not defined, so theelsepart is executed and it calls the kernel Makefile, passing the module directory in theMvariable; - The second pass is by the Kbuild system, which is initiated by the parameterized
makein thealltarget (it will go into$(LINUX_KERNEL_PATH)and call the top-level Makefile, which definesKERNELRELEASE). The top-level Makefile knows how to compile a kernel module and recursively calls the Makefile in the current directory (thanks to theMvariable), but this timeKERNELRELEASEis defined, so theifpart is executed.
External modules tend to place header files in a separate include/ directory. To inform Kbuild of the
directory, use ccflags-y flag.
Example
obj-m := 8123.o
ccflags-y := -I$(Include_DIR)
8123-y := 8123_if.o 8123_pci.o 8123_bin.oThis flag only applies to the Kbuild file in which it is assigned. It is used for the compiler.
This flag only applies to commands in the Kbuild file in which it is assigned. It specifies per-file options associated with file ${filename} for the compiler.
Example
CFLAGS_my_file1.o = -DDEBUG
CFLAGS_your_file2.o = -I$(src)/includeThese two lines specify compilation flags for my_file1.o and your_file2.o.
We can pass arguments to kernel modules.
module_param(
name, /* name of an already defined variable */
type, /* data type */
perm /* permission mask, for exposing parameters in sysfs (if non-zero) at a later stage */
);
MODULE_PARM_DESC(name, "Description"); /* Description of the parameter */The variable will be set to the value passed to the kernel module.
Example
#define DEFAULT_PARAM1 100
#define DEFAULT_PARAM2 200
int param1 = DEFAULT_PARAM1;
int param2 = DEFAULT_PARAM2;
// Get the parameters.
module_param(param1, int, 0);
module_param(param2, int, 0);
static int __init my_init(void)
{
if (param1 == DEFAULT_PARAM1) {
printk(KERN_INFO "Nothing passed or Default Value :%d: for param1 is passed\n", DEFAULT_PARAM1);
} else {
printk(KERN_INFO "param1 passed is :%d:", param1);
}
if (param2 == DEFAULT_PARAM2) {
printk(KERN_INFO "Nothing passed or Default Value :%d: for param2 is passed\n", DEFAULT_PARAM2);
} else {
printk(KERN_INFO "param2 passed is :%d:", param2);
}
return 0;
}sudo insmod my_module.ko param1=1000Modules parameter arrays are also possible with module_param_array().
The parameters of a module can be found in /sys/module/$(module_name)/parameters/, which contains individual files that are each individual parameters of the module that are able to be changed at runtime.
The permission mask dictates who is allowed to do what with the parameter file under /sys/module/. If you create a parameter with a permission setting of 0, that means that parameter will not show up under /sys/module/ at all, so no one will have any read or write access to it whatsoever (not even root). The only use that parameter will have is that you can set it at module load time, and that’s it.
The permission 0660 (an octal number) indicates read-write access for the owner and group and no access for others.
module_param(param1, int, 0660);You can change a parameter value at runtime like this:
sudo sh -c "echo 42 > /sys/module/my_module/parameters/param1"Please note that if you choose to define writable parameters and really do write/update them at runtime, your module is not informed that the value has changed. That is, there is no callback or notification mechanism for modified parameters; the value will quietly change in your module while your code keeps running, oblivious to the fact that there’s a new value in that variable (if you truly need write access to your module and some sort of notification mechanism, you probably don’t want to use module parameters for this purpose).