Writing a new kernel module¶
Introduction¶
A notable aspect of the Linux kernel is its ability to facilitate the dynamic loading (and unloading) of kernel modules. These modules can be added or removed while the system is running, eliminating the necessity for a system reboot.
This tutorial is divided into two parts. In the first part, we will write a simple kernel module that will print debug messages to the kernel log. In the second part, we will improve the module by manipulating the pseudo file system. Before we begin, make sure that you have the adapted environment to develop a module.
Setting up the environment¶
Danger
For the purpose of this tutorial, we will use the Linux kernel version 4.15.0. If you are using a different version, some of the code may not work as expected. Using another kernel version can lead to a null mark for the second project.
- There are two approaches to configuring your development environment:
Develop and build directly on the reference Virtual Machine: use
vim
ornano
to write your code andmake
to compile it. We will use this approach in this tutorial. The advantage of this method is that you don’t need to transfer your code to the Virtual Machine, but you will need to use the command line to write your code and compile it.Develop on your host machine and use
scp
orrsync
to transfer your code to the reference Virtual Machine. Once transferred, usessh
to connect to the Virtual Machine and usemake
to compile your code. The advantage of this method is that you can use your favorite IDE (such as VScode, CLion) to write your code.
For both methods, make sure that you have installed the following packages on your reference Virtual Machine:
sudo apt-get update
sudo apt install build-essential libncurses5-dev libssl-dev flex libelf-dev bison gcc make bc
Part 1: Basic kernel module¶
After configuring your development environment, you can start writing your kernel module.
Below is a basic example of a kernel module that logs a message to the system when it is loaded and unloaded. To implement this, create a file named module_info0940.c
and insert the following code:
#include <linux/init.h> // kernel initialization
#include <linux/kernel.h> // kernel logging
#include <linux/module.h> // kernel module
// sets the license for the module to the GNU General Public License
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Simple module that prints a message to the kernel log.");
// initialization function (constructor)
static int __init module_start(void){
printk(KERN_INFO "Hello, World!\n");
return 0;
}
// cleanup function (destructor)
static void __exit module_end(void){
printk(KERN_INFO "Goodbye, World!\n");
}
// register the initialization and cleanup functions
module_init(module_start);
module_exit(module_end);
To compile this module, you will need to create a Makefile
that specifies the module name and source files:
obj-m += module_info0940.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
The Makefile
provided is designed to compile a kernel module named module_info0940
using the source file module_info0940.c
. To compile the module, just execute the make
command in the directory where the Makefile
is located. Upon successful compilation, you will find a module_info0940.ko
file generated, representing the compiled kernel module. Additionally, you can use make clean
to tidy up the directory and delete any generated files.
Danger
If you have the error make[1]: *** /lib/modules/4.15.0/build: No such file or directory [...]
. Download and execute the following script to fix the issue related to the kernel headers.
# Download and execute the script ONLY if you have the previous error
wget --no-check-certificate https://people.montefiore.uliege.be/gain/courses/info0940/asset/fix_modules.sh && chmod +x fix_modules.sh && ./fix_modules.sh && rm fix_modules.sh
# Then re-run the make command to compile the module
Once the kernel is successful compiled, you can load and unload the module using respectively the insmod
and rmmod
commands. The dmesg
command can be used to view the kernel log and verify that the module has been loaded and unloaded.
sudo insmod module_info0940.ko
sudo rmmod module_info0940.ko
dmesg
You can check the system log using the dmesg
command or check the /var/log/syslog
log file to see the messages printed by the module which should be similar to the following:
[ 123.456789] Hello, World!
[ 123.456790] Goodbye, World!
Part 2: Manipulating the pseudo file system¶
In this part, we will improve the kernel module by manipulating the pseudo file system. We will create a file in the /proc
directory that will allow us to read and write data to the kernel module. This module automatically converts any message written to the file in the pseudo file system directory into uppercase letters.
As a reminder, the /proc
directory is a pseudo file system that provides an interface to kernel data structures. It is commonly used to communicate with the kernel and retrieve information about the system. The /proc
directory is mounted at boot time, and it is populated with various files and directories that provide information about the system.
To implement this, update the module_info0940.c
file and insert the following code:
#include<linux/fs.h> // file operations
#include<linux/proc_fs.h> // proc_create, proc_ops
#include<linux/uaccess.h> // copy_from_user, copy_to_user
#include<linux/init.h> // kernel initialization
#include<linux/seq_file.h> // seq_read, seq_lseek, single_open, single_release
#include<linux/module.h> // all modules need this
#include<linux/slab.h> // memory allocation (kmalloc/kzalloc)
#include<linux/kernel.h> // kernel logging
#define DEV_NAME "module_info0940" // name of the proc entry
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Creates /proc entry with read/write functionality.");
static char *message = NULL;
// this function converts a string to uppercase
void strtoupper(char *str) {
while (*str) {
if (*str > 'a' && *str < 'z')
*str -= ('a'-'A');
str++;
}
}
// this function writes a message to the pseudo file system
static ssize_t write_msg(struct file *file, const char __user *buff, size_t cnt, loff_t *f_pos){
// allocate memory, (size and flag) - flag: type of memory (kernel memory)
char *tmp = kzalloc(cnt + 1, GFP_KERNEL);
if(!tmp){
return -ENOMEM;
}
// copy data from user space to kernel space by using copy_from_user
if(copy_from_user(tmp, buff, cnt)){
kfree(tmp);
return -EFAULT;
}
if (message){
kfree(message);
}
// Convert lowercases to uppercases
strtoupper(tmp);
message=tmp;
return cnt;
}
// this function reads a message from the pseudo file system via the seq_printf function
static int show_the_proc(struct seq_file *a, void *v){
seq_printf(a,"%s\n",message);
return 0;
}
// this function opens the proc entry by calling the show_the_proc function
static int open_the_proc(struct inode *inode, struct file *file){
return single_open(file, show_the_proc, NULL);
}
/*-----------------------------------------------------------------------*/
// Structure that associates a set of function pointers (e.g., device_open)
// that implement the corresponding file operations (e.g., open).
/*-----------------------------------------------------------------------*/
static struct file_operations new_fops={ //defined in linux/fs.h
.owner = THIS_MODULE,
.open = open_the_proc, //open callback
.release = single_release,
.read = seq_read, //read
.write = write_msg, //write callback
.llseek = seq_lseek,
};
static int __init module_start(void){
// create proc entry with read/write functionality
struct proc_dir_entry *entry = proc_create(DEV_NAME, 0777, NULL, &new_fops);
if(!entry) {
return -1;
}else {
printk(KERN_INFO "Init Module [OK]\n");
}
return 0;
}
static void __exit module_stop(void){
if (message){
kfree(message);
}
// remove proc entry
remove_proc_entry(DEV_NAME, NULL);
printk(KERN_INFO "Exit Module [OK]\n");
}
module_init(module_start);
module_exit(module_stop);
To create a file in the /proc
directory, we will use the proc_create
function. This function takes three arguments: the name of the file, the file permissions, and a pointer to a proc_ops
structure that contains the file operations. The proc_ops
structure contains function pointers for the read and write operations, which are called when the file is read from or written to, respectively.
The write_msg
function is called when the file is written to. It allocates memory for the message, copies the data from the user space to the kernel space, and updates the message pointer. The show_the_proc
function is called when the file is read from. It prints the message to the file using the seq_printf
function.
To compile this module, you can use the same Makefile
as before. Once the module is compiled, you can load and unload it using the insmod
and rmmod
commands, respectively. You can also read and write to the file in the /proc
directory using the cat
and echo
commands.
After loading the module, you can write a message to the file (/proc/module_info0940
) using the following command:
echo "Hello, World! " > /proc/module_info0940
You can then read the message from the file using the following command:
cat /proc/module_info0940
which will give you the following output:
HELLO, WORLD!
Finally, unload the module by using the rmmod
command. After unloading the module, the file in the /proc
directory will no longer exist. If you try to read from or write to the file, you will receive an error message.
sudo rmmod module_info0940
cat /proc/module_info0940
cat: /proc/module_info0940: No such file or directory
What’s the next step?¶
You have now successfully written a simple kernel module that prints a message to the kernel log and created a file in the /proc
directory that allows you to read and write data to the kernel module. You can now use this knowledge to develop more complex kernel modules that interact with the kernel and provide additional functionality.
For instance, you can develop a kernel module that interacts with processes and creates data structures to store them (see list example in session 4). You can also explore the Linux kernel source code to understand how the kernel works and how to develop kernel modules that interact with the kernel.
You can have a look at the following resources to learn more about kernel modules and the Linux kernel:
Good luck with your kernel module development!