Writing a Pointless Linux Module

Writing your first Linux module can appear to be a very daunting and challenging task. That’s mainly because it is. But, hey if it was easy, then everyone would do it.

Photo by Sai Kiran Anagani on Unsplash

Getting Started

In this post, you will hopefully create a very simple Linux kernel module that outputs some traditional messages like “Hello, World!” and “Goodbye, World!”.

I’ll try to focus on explaining the details of every line of code, as opposed to writing a complex module without much explanation.

Like most coding projects, there are some prerequisite tools that you need to install before developing. Luckily for this project, there aren’t that many, but you’ll still need to have access to the following:

  • Linux machine
  • make installed
  • sudo access

Header Files

To get started, we’ll need to first #include some header files to get the bare-minimum functionality of our “Hello, World!” module. We can do so by creating a file named hello_module.c with the following contents:

#include  // Allows us to define Macros to replace init_module() and cleanup_module()
#include  // Gives us access to KERN_INFO

The header file will basically allow us to define what our description does, while the header file will allow us to provide output from our module.

How to Init

Next, we’ll need to define how the kernel is supposed to initialize our module. We can do so by creating a static init function (you can use whatever function name you want):

static int __init hello_world_init(void)
{
/* This is the entry initialization function of our module that runs when the module is first inserted into the kernel */
  return 0; // 0 => Indicate success to the kernel
}

Inside this function, we’ll want to add the code that prints the “Hello, World!” remark. One important drawback to note is that we can’t use the traditional printf() function, as the kernel has no header file that defines it. Instead, we’ll use a function calledprintk() which offers a similar way of printing.

Moreover, we can output “Hello, World!” to the kernel ring buffer (this is what KERN_INFOdoes) from our module by adding the following to our init function:

printk(KERN_INFO "Hello, World!");

This brings the total code so far to the following:

#include 
#include
static int __init hello_world_init(void)
{
printk(KERN_INFO "Hello, World!");
return 0;
}

How to Exit

Now that we have successfully provided an initialization point that runs when our module is loaded, we will also have to define the exit point from when our module is unloaded or removed.

This time we can define the exit function with the following:

static void __exit hello_world_exit(void)
{
/* This is the function that will be ran when the module is being unloaded from the kernel */
}

This time we’ll want to add the code that prints the “Goodbye, World!” in this function. We can do so in the same manner as before, with the following:

printk(KERN_INFO "Goodbye, World!");

Adding this line will bring the current exit function to:

static void __exit hello_world_exit(void)
{
printk(KERN_INFO "Goodbye, World!");
}

Notice how we didn’t add the return 0; in this function? This is because we defined the function that specified it wouldn’t return anything with the C void keyword.

If you followed all of the aforementioned steps, you should have come up with the following code:

#include 
#include
static int __init hello_world_init(void)
{
printk(KERN_INFO "Hello, World!n");
return 0;
}
static void __exit hello_world_exit(void)
{
printk(KERN_INFO "Goodbye, World!n");
}

Although this code looks pretty solid, it won’t compile successfully for a few reasons. The first of being that we have to tell the kernel that we aren’t using the default init and exit function names. We also still need to create the Makefile that will compile our code, and turn it into a .ko file that the kernel can actually understand. Lastly, we will need to define macros which specify various details of our module.

Defining the custom init and exit functions

As mentioned previously, we’re not using the default entry and exit points in a Linux kernel module. This is because I think it will be slightly easier for beginners to grasp.

Anyways, since we included the header file, all we have to do to define the custom init and exit functions is add the following to the bottom of our module:

module_init(hello_world_init);
module_exit(hello_world_exit);

This is self-explanatory code, but if you’re like me you’ll still want an explanation. So, the line module_init(hello_world_init); basically provides a pass-by-reference value of the hello_world_init static function(this is why the init function is defined with the static keyword) to the module_init(); function.

The module_exit(); function does the same exit thing, but with the hello_world_exit reference.

With the addition of the two previous lines of code, our total code now stands with this:

#include 
#include
static int __init hello_world_init(void)
{
printk(KERN_INFO "Hello, World!n");
return 0;
}
static void __exit hello_world_exit(void)
{
printk(KERN_INFO "Goodbye, World!n");
}
module_init(hello_world_init);
module_exit(hello_world_exit);

Creating the Makefile

Now instead of running a bunch of commands to compile our module in a specific way, we can just define a Makefile which will automate this compilation process for us. I’m not going to go much in detail about how and why we do what we do in this Makefile

But the key-take-away value is that it compiles our module to the requirements of a Linux kernel module using some built-in tools on Linux.

Now, let’s create the Makefile by simply creating a file named-so, and add the following contents:

obj-m += hello_module.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

In order to make use of this Makefile we can simply type and run make command.

Once this command finishes executing, we should a few files in our current directory. If all goes to plan, the compiled kernel module will be named hello_module.ko

Inserting the module

Now it’s time to actually insert our module into the kernel!

This is a rather simple process thanks to built-in commands in Linux. You can insert your module by running:

sudo insmod hello_module.ko

insmod simply stands for “insert module”

If your computer doesn’t catch fire, then your module has most likely been inserted.

Viewing the results

If you want to see if the module has actually worked (your Linux machine would crash if you did something wrong when inserting), then simply run the following:

dmesg | grep "Hello, World!"

If you see “Hello, World!” outputted back at you, then your module has been successfully loaded into the kernel!!! Although, if you don’t see the magical “Hello, World!” then make sure to re-read the tutorial.

Removing the module

Once your excitement of writing and loading your first Linux kernel module wears off, you might want to remove it from the kernel. I mean it’s surely not doing it too many favors!

To remove the module, we can simply run:

sudo rmmod hello_module

rmmod stands for “remove module”

Viewing the results again!

Again, if you wanted to view the results of removing your module from the Linux kernel, then simply run:

dmesg

If you see the “Goodbye, World!” message, then you know that your module has successfully unloaded from the kernel.

Defining our module

Lastly, we’ll want to define what our module does, that way your users can grasp a better understanding of it if they were to use it.

In order to start defining our module, we’ll need to include another header file that allows us to define macros. So, add #include into the top of your hello_module.c file.

Next, we need to define some macros which define our module. We can do so by adding the following right under all of our #include statements:

#define MOD_AUTHOR "Nathan Carnegie"
#define MOD_DESCRIPTION "Pointless Module"

After adding these macros, we’ll want to call the defined macros from the included header file that will actually make use of our macros.

We can do so by adding the following code to the bottom of our module:

MODULE_AUTHOR(MOD_AUTHOR);
MODULE_DESCRIPTION(MOD_DESCRIPTION);

We can also define what license our module is under by making use of adding the following macro below:

MODULE_LICENSE("GPL");

Adding all of the aforementioned code in this tutorial will result in:

#include 
#include
#include
#define MOD_AUTHOR "Nathan Carnegie"
#define MOD_DESCRIPTION "Pointless Module"
static int __init hello_world(void)
{
printk(KERN_INFO "Hello, World!n");
return 0;
}
static void __exit hello_world_exit(void)
{
printk(KERN_INFO "Goodbye, World!n");
}
MODULE_AUTHOR(MOD_AUTHOR);
MODULE_DESCRIPTION(MOD_DESCRIPTION);
MODULE_LICENSE("GPL");
module_init(hello_world);
module_exit(hello_world_exit);

After running make one last time, we can now insert our newly improved module(you need to remove the previous module with rmmod first). Again we can do so by running the following:

sudo insmod hello_module.ko

Lastly, we can view the newly defined information regarding our module, by making use of modinfo

Just run the following to get the details of the module:

modinfo hello_module.ko

Where to go from here?

Reaching this point in the post, you have most likely created your first kernel module and are now looking for more information to continue your development.

If this is the case, I recommend obtaining “Linux Kernel Development“ by Robert Love which can be found here (← That’s an affiliate link).

Thanks for reading!

Credit: Some Information was expanded upon from the “Linux Kernel Development” book (← Same affiliate link), and from tldp!

read original article here