An Introduction to the Linux Kernel
This article provides an introduction to the Linux kernel, and demonstrates how to write and compile a module.
Have you ever wondered how a computer manages the most complex tasks with such efficiency and accuracy? The answer is, with the help of the operating system. It is the operating system that uses hardware resources efficiently to perform various tasks and ultimately makes life easier. At a high level, the OS can be divided into two partsthe first being the kernel and other is the utility programs. Various user space processes ask for system resources such as the CPU, storage, memory, network connectivity, etc, and the kernel services these requests. This column will explore loadable kernel modules in GNU/Linux.
The Linux kernel is monolithic, which means that the entire OS runs solely in supervisor mode. Though the kernel is a single process, it consists of various subsystems and each subsystem is responsible for performing certain tasks. Broadly, any kernel performs the following main tasks.
Process management: This subsystem handles the process life-cycle. It creates and destroys processes, allowing communication and data sharing between processes through inter-process communication (IPC). Additionally, with the help of the process scheduler, it schedules processes and enables resource sharing.
Memory management: This subsystem handles all memory related requests. Available memory is divided into chunks of a fixed size called pages, which are allocated or de-allocated to/from the process, on demand. With the help of the memory management unit (MMU), it maps the process virtual address space to a physical address space and creates the illusion of a contiguous large address space.
File system: The GNU/Linux system is heavily dependent on the file system. In GNU/Linux, almost everything is a file. This subsystem handles all storage related requirements like the creation and deletion of files, compression and journaling of data, the organisation of data in a hierarchical manner, and so on. The Linux kernel supports all major file systems including MS Windows NTFS.
Device control: Any computer system requires various devices. But to make the devices usable, there should be a device driver and this layer provides that functionality. There are various types of drivers present, like graphics drivers, a Bluetooth driver, audio/video drivers and so on.
Networking: Networking is one of the important aspects of any OS. It allows communication and data transfer between hosts. It collects, identifies and transmits network packets. Additionally, it also enables routing functionality.
Dynamically loadable kernel modules
We often install kernel updates and security patches to make sure our system is up-to-date. In case of MS Windows, a reboot is often required, but this is not always acceptable; for instance, the machine cannot be rebooted if is a production server. Wouldnt it be great if we could add or remove functionality to/from the kernel on-the-fly without a system reboot? The Linux kernel allows dynamic loading and unloading of kernel modules. Any piece of code that can be added to the kernel at runtime is called a kernel module. Modules can be loaded or unloaded while the system is up and running without any interruption. A kernel module is an object code that can be dynamically linked to the running kernel using the insmod command and can be unlinked using the rmmod command.
We often install kernel updates and security patches to make sure our system is up-to-date. In case of MS Windows, a reboot is often required, but this is not always acceptable; for instance, the machine cannot be rebooted if is a production server. Wouldnt it be great if we could add or remove functionality to/from the kernel on-the-fly without a system reboot? The Linux kernel allows dynamic loading and unloading of kernel modules. Any piece of code that can be added to the kernel at runtime is called a kernel module. Modules can be loaded or unloaded while the system is up and running without any interruption. A kernel module is an object code that can be dynamically linked to the running kernel using the insmod command and can be unlinked using the rmmod command.
A few useful utilities
GNU/Linux provides various user-space utilities that provide useful information about the kernel modules. Let us explore them.
lsmod: This command lists the currently loaded kernel modules. This is a very simple program which reads the /proc/modules file and displays its contents in a formatted manner.
insmod: This is also a trivial program which inserts a module in the kernel. This command doesnt handle module dependencies.
GNU/Linux provides various user-space utilities that provide useful information about the kernel modules. Let us explore them.
lsmod: This command lists the currently loaded kernel modules. This is a very simple program which reads the /proc/modules file and displays its contents in a formatted manner.
insmod: This is also a trivial program which inserts a module in the kernel. This command doesnt handle module dependencies.
rmmod: As the name suggests, this command is used to unload modules from the kernel. Unloading is done only if the current module is not in use. rmmod also supports the -f or --forceoption, which can unload modules forcibly. But this option is extremely dangerous. There is a safer way to remove modules. With the -w or --wait option, rmmod will isolate the module and wait until the module is no longer used.
modinfo: This command displays information about the module that was passed as a command-line argument. If the argument is not a filename, then it searches the /lib/modules/<version>directory for modules. modinfo shows each attribute of the module in the field:value format.
Note: <version> is the kernel version. We can obtain it by executing the uname -r command.
Note: <version> is the kernel version. We can obtain it by executing the uname -r command.
dmesg: Any user-space program displays its output on the standard output stream, i.e.,/dev/stdout but the kernel uses a different methodology. The kernel appends its output to the ring buffer, and by using the dmesg command, we can manage the contents of the ring buffer.
Preparing the system
Now its time for action. Lets create a development environment. In this section, lets install all the required packages on an RPM-based GNU/Linux distro like CentOS and a Debian-based GNU/Linux distro like Ubuntu.
Installing CentOS
First install the gcc compiler by executing the following command as a root user:
Preparing the system
Now its time for action. Lets create a development environment. In this section, lets install all the required packages on an RPM-based GNU/Linux distro like CentOS and a Debian-based GNU/Linux distro like Ubuntu.
Installing CentOS
First install the gcc compiler by executing the following command as a root user:
[root] # yum -y install gcc |
Then install the kernel development packages:
[root] # yum -y install kernel-devel |
Finally, install the make utility:
[root] # yum -y install make |
Installing Ubuntu
First install the gcc compiler:
First install the gcc compiler:
[mickey] sudo apt-get install gcc |
After that, install kernel development packages:
[mickey] sudo apt-get install kernel-package |
And, finally, install the make utility:
[mickey] sudo apt-get install make |
Our first kernel module
Our system is ready now. Let us write the first kernel module. Open your favourite text editor and save the file as hello.c with the following contents:
Our system is ready now. Let us write the first kernel module. Open your favourite text editor and save the file as hello.c with the following contents:
#include <linux/kernel.h> #include <linux/module.h> int init_module(void) { printk(KERN_INFO Hello, World !!!\n); return 0; } void cleanup_module(void) { printk(KERN_INFO Exiting ...\n); } MODULE_LICENSE(GPL); MODULE_AUTHOR(Narendra Kangralkar.); MODULE_DESCRIPTION(Hello world module.); MODULE_VERSION(1.0); |
Any module must have at least two functions. The first is initialisation and the second is the clean-up function. In our case, init_module() is the initialisation function and cleanup_module() is the clean-up function. The initialisation function is called as soon as the module is loaded and the clean-up function is called just before unloading the module. MODULE_LICENSE and other macros are self-explanatory.
There is a printk() function, the syntax of which is similar to the user-space printf() function. But unlike printf() , it doesnt print messages on a standard output stream; instead, it appends messages into the kernels ring buffer. Each printk() statement comes with a priority. In our example, we used the KERN_INFO priority. Please note that there is no comma (,) between KERN_INFO and the format string. In the absence of explicit priority,DEFAULT_MESSAGE_LOGLEVEL priority will be used. The last statement in init_module() isreturn 0 which indicates success.
The names of the initialisation and clean-up functions are init_module() and cleanup_module()respectively. But with the new kernel (>= 2.3.13) we can use any name for the initialisation and clean-up functions. These old names are still supported for backward compatibility. The kernel provides module_init and module_exit macros, which register initialisation and clean-up functions. Let us rewrite the same module with names of our own choice for initialisation and cleanup functions:
#include <linux/kernel.h> #include <linux/module.h> static int __init hello_init(void) { printk(KERN_INFO Hello, World !!!\n); return 0; } static void __exit hello_exit(void) { printk(KERN_INFO Exiting ...\n); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE(GPL); MODULE_AUTHOR(Narendra Kangralkar.); MODULE_DESCRIPTION(Hello world module.); MODULE_VERSION(1.0); |
Here, the __init and __exit keywords imply initialisation and clean-up functions, respectively.
Compiling and loading the module
Now, let us understand the module compilation procedure. To compile a kernel module, we are going to use the kernels build system. Open your favourite text editor and write down the following compilation steps in it, before saving it as Makefile. Please note that the kernel modules hello.c and Makefile must exist in the same directory.
Now, let us understand the module compilation procedure. To compile a kernel module, we are going to use the kernels build system. Open your favourite text editor and write down the following compilation steps in it, before saving it as Makefile. Please note that the kernel modules hello.c and Makefile must exist in the same directory.
obj-m += hello.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 |
To build modules, kernel headers are required. The above makefile invokes the kernels build system from the kernels source and finally the kernels makefile invokes our Makefile to compile the module. Now that we have everything to build our module, just execute the make command, and this will compile and create the kernel module named hello.ko:
[mickey] $ ls hello.c Makefile [mickey]$ make make -C /lib/modules/2 .6.32-358.el6.x86_64 /build M= /home/mickey modules make [1]: Entering directory ` /usr/src/kernels/2 .6.32-358.el6.x86_64 CC [M] /home/mickey/hello .o Building modules, stage 2. MODPOST 1 modules CC /home/mickey/hello .mod.o LD [M] /home/mickey/hello .ko.unsigned NO SIGN [M] /home/mickey/hello .ko make [1]: Leaving directory ` /usr/src/kernels/2 .6.32-358.el6.x86_64 [mickey]$ ls hello.c hello.ko hello.ko.unsigned hello.mod.c hello.mod.o hello.o Makefile modules.order Module.symvers |
We have now successfully compiled our first kernel module. Now, let us look at how to load and unload this module in the kernel. Please note that you must have super-user privileges to load/unload kernel modules. To load a module, switch to the super-user mode and execute theinsmod command, as shown below:
[root] # insmod hello.ko |
insmod has done its job successfully. But where is the output? It is appended to the kernels ring buffer. So lets verify it by executing the dmesg command:
[root] # dmesg Hello, World !!! |
We can also check whether our module is loaded or not. For this purpose, lets use the lsmod command:
[root] # lsmod | grep hello hello 859 0 |
To unload the module from the kernel, just execute the rmmod command as shown below and check the output of the dmesg command. Now, dmesg shows the message from the clean-up function:
[root] # rmmod hello [root] # dmesg Hello, World !!! Exiting ... |
In this module, we have used a couple of macros, which provide information about the module. The modinfo command displays this information in a nicely formatted fashion:
[mickey]$ modinfo hello.ko filename: hello.ko version: 1.0 description: Hello world module. author: Narendra Kangralkar. license: GPL srcversion: 144DCA60AA8E0CFCC9899E3 depends: vermagic: 2.6.32-358.el6.x86_64 SMP mod_unload modversions |
Finding the PID of a process
Let us write one more kernel module to find out the Process ID (PID) of the current process. The kernel stores all process related information in the task_struct structure, which is defined in the<linux/sched.h> header file. It provides a current variable, which is a pointer to the current process. To find out the PID of a current process, just print the value of the current->pid variable. Given below is the complete working code (pid.c):
Let us write one more kernel module to find out the Process ID (PID) of the current process. The kernel stores all process related information in the task_struct structure, which is defined in the<linux/sched.h> header file. It provides a current variable, which is a pointer to the current process. To find out the PID of a current process, just print the value of the current->pid variable. Given below is the complete working code (pid.c):
#include <linux/kernel.h> #include <linux/module.h> #include <linux/sched.h> static int __init pid_init(void) { printk(KERN_INFO pid = %d\n, current->pid); return 0; } static void __exit pid_exit(void) { /* Dont do anything */ } module_init(pid_init); module_exit(pid_exit); MODULE_LICENSE(GPL); MODULE_AUTHOR(Narendra Kangralkar.); MODULE_DESCRIPTION(Kernel module to find PID.); MODULE_VERSION(1.0); |
The Makefile is almost the same as the first makefile, with a minor change in the object files name:
obj-m += pid.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 |
Now compile and insert the module and check the output using the dmesg command:
[mickey]$ make [root] # insmod pid.ko [root] # dmesg pid = 6730 |
A module that spans multiple files
So far we have explored how to compile a module from a single file. But in a large project, there are several source files for a single module and, sometimes, it is convenient to divide the module into multiple files. Let us understand the procedure of building a module that spans two files. Lets divide the initialization and cleanup functions from the hello.c file into two separate files, namely startup.c and cleanup.c.
Given below is the source code for startup.c:
So far we have explored how to compile a module from a single file. But in a large project, there are several source files for a single module and, sometimes, it is convenient to divide the module into multiple files. Let us understand the procedure of building a module that spans two files. Lets divide the initialization and cleanup functions from the hello.c file into two separate files, namely startup.c and cleanup.c.
Given below is the source code for startup.c:
#include <linux/kernel.h> #include <linux/module.h> static int __init hello_init(void) { printk(KERN_INFO Function: %s from %s file \n, __func__, __FILE__); return 0; } module_init(hello_init); MODULE_LICENSE(GPL); MODULE_AUTHOR(Narendra Kangralkar.); MODULE_DESCRIPTION(Startup module.); MODULE_VERSION(1.0); |
And cleanup.c will look like this.
#include <linux/kernel.h> #include <linux/module.h> static void __exit hello_exit(void) { printk(KERN_INFO Function %s from %s file \n, __func__, __FILE__); } module_exit(hello_exit); MODULE_LICENSE(BSD); MODULE_AUTHOR(Narendra Kangralkar.); MODULE_DESCRIPTION(Cleanup module.); MODULE_VERSION(1.1); |
Now, here is the interesting part – Makefile for these modules:
obj-m += final.o final-objs := startup.o cleanup.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 is self-explanatory. Here, we are saying: Build the final kernel object by usingstartup.o and cleanup.o. Let us compile and test the module:
[mickey]$ ls cleanup.c Makefile startup.c [mickey]$ make |
Then, lets display module information using the modinfo command:
[mickey]$ modinfo final.ko filename: final.ko version: 1.0 description: Startup module. author: Narendra Kangralkar. license: GPL version: 1.1 description: Cleanup module. author: Narendra Kangralkar. license: BSD srcversion: D808DB9E16AC40D04780E2F depends: vermagic: 2.6.32-358.el6.x86_64 SMP mod_unload modversions |
Here, the modinfo command shows the version, description, licence and author-related information from each module.
Let us load and unload the final.ko module and verify the output:
Let us load and unload the final.ko module and verify the output:
[mickey]$ su - Password: [root] # insmod final.ko [root] # dmesg Function: hello_init from /home/mickey/startup .c file [root] # rmmod final [root] # dmesg Function: hello_init from /home/mickey/startup .c file Function hello_exit from /home/mickey/cleanup .c file |
Passing command-line arguments to the module
In user-space programs, we can easily manage command line arguments with argc/ argv. But to achieve the same functionality through modules, we have to put in more of an effort.
To achieve command-line handling in modules, we first need to declare global variables and use the module_param() macro, which is defined in the <linux/moduleparam.h> header file. There is also the MODULE_PARM_DESC() macro which provides descriptions about arguments. Without going into lengthy theoretical discussions, let us write the code:
In user-space programs, we can easily manage command line arguments with argc/ argv. But to achieve the same functionality through modules, we have to put in more of an effort.
To achieve command-line handling in modules, we first need to declare global variables and use the module_param() macro, which is defined in the <linux/moduleparam.h> header file. There is also the MODULE_PARM_DESC() macro which provides descriptions about arguments. Without going into lengthy theoretical discussions, let us write the code:
#include <linux/kernel.h> #include <linux/module.h> #include <linux/moduleparam.h> static char *name = Narendra Kangralkar; static long roll_no = 1234; static int total_subjects = 5; static int marks[5] = {80, 75, 83, 95, 87}; module_param(name, charp, 0); MODULE_PARM_DESC(name, Name of the a student); module_param(roll_no, long, 0); MODULE_PARM_DESC(rool_no, Roll number of a student); module_param(total_subjects, int, 0); MODULE_PARM_DESC(total_subjects, Total number of subjects); module_param_array(marks, int, &total_subjects, 0); MODULE_PARM_DESC(marks, Subjectwise marks of a student); static int __init param_init(void) { static int i; printk(KERN_INFO Name : %s\n, name); printk(KERN_INFO Roll no : %ld\n, roll_no); printk(KERN_INFO Subjectwise marks ); for (i = 0; i < total_subjects; ++i) { printk(KERN_INFO Subject-%d = %d\n, i + 1, marks[i]); } return 0; } static void __exit param_exit(void) { /* Dont do anything */ } module_init(param_init); module_exit(param_exit); MODULE_LICENSE(GPL); MODULE_AUTHOR(Narendra Kangralkar.); MODULE_DESCRIPTION(Module with command line arguments.); MODULE_VERSION(1.0); |
After compilation, first insert the module without any arguments, which display the default values of the variable. But after providing command-line arguments, default values will be overridden. The output below illustrates this:
[root] # insmod parameters.ko [root] # dmesg Name : Narendra Kangralkar Roll no : 1234 Subjectwise marks Subject-1 = 80 Subject-2 = 75 Subject-3 = 83 Subject-4 = 95 Subject-5 = 87 [root] # rmmod parameters |
Now, let us reload module with command-line arguments and verify the output.
[root] # insmod ./parameters.ko name=Mickey roll_no=1001 marks=10,20,30,40,50 [root] # dmesg Name : Mickey Roll no : 1001 Subjectwise marks Subject-1 = 10 Subject-2 = 20 Subject-3 = 30 Subject-4 = 40 Subject-5 = 50 |
If you want to learn more about modules, the Linux kernels source code is the best place to do so. You can download the latest source code from https://www.kernel.org/. Additionally, there are a few good books available in the market like Linux Kernel Development (3rd Edition) by Robert Love and Linux Device Drivers (3rd Edition). You can also download the free book from http://lwn.net/Kernel/LDD3/.