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: 1. Develop and build directly on the reference Virtual Machine: use ``vim`` or ``nano`` to write your code and ``make`` 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. 2. Develop on your host machine and use ``scp`` or ``rsync`` to transfer your code to the reference Virtual Machine. Once transferred, use ``ssh`` to connect to the Virtual Machine and use ``make`` 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: .. code-block:: shell 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: .. code-block:: c #include // kernel initialization #include // kernel logging #include // 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: .. code-block:: shell 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. .. code-block:: shell # 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. .. code-block:: shell 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: .. code-block:: shell [ 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: .. code-block:: C #include // file operations #include // proc_create, proc_ops #include // copy_from_user, copy_to_user #include // kernel initialization #include // seq_read, seq_lseek, single_open, single_release #include // all modules need this #include // memory allocation (kmalloc/kzalloc) #include // 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: .. code-block:: shell echo "Hello, World! " > /proc/module_info0940 You can then read the message from the file using the following command: .. code-block:: shell cat /proc/module_info0940 which will give you the following output: .. code-block:: shell 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. .. code-block:: shell 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: - https://tldp.org/LDP/lkmpg/2.6/html/ - https://sysprog21.github.io/lkmpg/ - https://www.kernel.org/doc/html/latest/index.html - https://elixir.bootlin.com/linux/v4.15/source Good luck with your kernel module development!