In the last article, we examined the basics of
Linux kernel modules in theory. Starting from this article, we will make our hands dirty and start implementing our mini-firewall. We will walk through the whole process step by step. In this article, let’s write our first Linux kernel module using a simple
hello world demo. Then let’s learn how to build the module(which is very different from compiling an application in the user space) and how to load it in the kernel. After understanding how to write a module, in the next article, let’s write the initial version of our mini-firewall module using Netfilter’s hook architecture. All right. Let’s start the journey.
First, I have to admit that Linux Kernel module development is a kind of large and complex technology topic. And there are many great online resources about it. This series of articles is focusing on developing the mini-firewall based on Netfilter, so we can’t cover all the aspects of the Kernel module itself. In future articles, I’ll examine more in-depth knowledge of kernel modules.
You can write the
hello world Kernel module with a single
C source code file
hello.c as follows:
We can write a Kernel module in such an easy and simple way because the Linux Kernel does the magic for you. Remember the design philosophy of Linux(Unix): Design for simplicity; add complexity only where you must.
Let’s examine several technical points worth to remark as follows:
First, Kernel modules must have at least two functions: a “start” function which is called when the module is loaded into the kernel, and an “end” function which is called just before it is removed from the kernel. Before kernel 2.3.13, the names of these two functions are hardcoded as
cleanup_module(). But in the new versions, you can use whatever name you like for the start and end functions of a module by using the
module_exit macros. The macros are defined in
include/linux/init.h. You can refer there for detailed information.
module_init either registers a handler for something with the kernel (for example, the mini-firewall developed in this article), or it replaces one of the kernel functions with its own code (usually code to do something and then call the original function). The
module_exit function is supposed to undo whatever
module_init did, so the module can be unloaded safely.
printk function provides similar behaviors to
printf, which accepts the
format string as the first argument. The
printk function prototype goes as follows:
int printk(const char *fmt, ...);
printk function allows a caller to specify
log level to indicate the type and importance of the message being sent to the kernel message log. For example, in the above code, the log level
KERN_INFO is specified by prepending to the format string. In C programming, this syntax is called
string literal concatenation. (In other high-level programming languages, string concatenation is generally done with
+ operator). For the function
log level, you can find more information in
Note: The path to header files for Linux kernel module development is different from the one you often used for the application development. Don’t try to find the header file inside /usr/include/linux, instead please use the following path /lib/modules/`uname -r`/build/include/linux (
uname -r command returns your kernel version).
Next, let’s build this hello-world kernel module.
The way to build a kernel module is a little different from how to build a user-space application. The efficient solution to build kernel image and its modules is
Kernel Build System(Kbuild).
Kbuild is a complex topic and I won’t explain it in too much detail here. Simply speaking,
Kbuild allows you to create highly customized kernel binary images and modules. Technically, each subdirectory contains a
Makefile compiling only the source code files in its directory. And a top-level Makefile recursively executes each subdirectory’s Makefile to generate the binary objects. And you can control which subdirectories are included by defining
config files. In detail, you can refer to other documents.
The following is the Makefile for the
hello world module:
obj-m += hello.o
make -C dir command changes to directory dir before reading the makefiles or doing anything else. The top-level Makefile in /lib/modules/$(shell uname -r)/build will be used. You can find that command
make M=dir modules is used to make all modules in specified dir.
And in the module-level Makefile, the
obj-m syntax tells
kbuild system to build
module_name.c, and after linking, will result in the kernel module
module_name.ko. In our case, the module name is
The build process goes as follows:
chrisbao:~/develop/kernel/hello-1$ sudo make
After the build, you can get several new files in the same directory:
The file ends with
.ko is the kernel module. You can ignore other files now, I will write another article later to have a deep discussion about the kernel module system.
file command, you can note that the kernel module is an
ELF(Executable and Linkable Format) format file. ELF files are typically the output of a compiler or linker and are a binary format.
chrisba:~/develop/kernel/hello-1$ file hello.ko
Next step, let’s try to install and remove the module dynamically. You need to know the following three commands:
- lsmod: shows the list of kernel modules currently loaded.
- insmod: inserts a module into the Linux Kernel by running
sudo insmod module_name.ko
- rmmod: removes a module from the Linux Kernel by running
sudo rmmod module_name
hello world module is quite simple, you can easily install and remove the module as you wish. I will not show the detailed commands here and leave it to the readers.
Note: It doesn’t mean that you can easily install and remove any kernel module without any issues. If the module you are loading has bugs, the entire system can crash.
Next step, let’s prove that the
hello world module is installed and removed as expected. We will use
dmesg (diagnostic messages) can print the messages in the
kernel ring buffer.
ring buffer is a data structure that uses a single, fixed-size buffer as if it were connected end-to-end. The
kernel ring buffer is a ring buffer that records messages related to the operation of the kernel. As we mentioned above, the kernel logs printed by the
printk function will be sent to the kernel ring buffer.
We can find the messages produced by our module with command
dmesg | grep world as follows:
chrisbao:~$ dmesg | grep world
Now you can see that the
hello world is loaded into the kernel correctly. And it can be removed dynamically as well. Great.
In this article, we examine how to write a kernel module, how to build it and how to install it into the kernel dynamically. Next article we can work on the mini-firewall as a