blog

Diving into the Linux kernel and making a kernel module

My goal for the next few days/weeks is to dive deeper into the Linux kernel and learn how it works, and I will document whatever I make here. Why? Because I can!

So, first of all, what is the kernel? The kernel is a little program, usually named vmlinuz-[version number]. It has around 5Mb and resides comfortably in your /boot directory, if you are using linux. This program gets loaded by a bootloader (one of the most popular is Grub), which I will learn more later on. The bootloader will pass parameters to the kernel, and in return, the kernel will provide an API, to which we can make System Calls - usually, done by the Standard C Library (in this post I built the GNU C Library from source code, if you want to see it). Another way to interact with the kernel, aside from its API, is through a virtual file system - I am planning to make another post about this later on.

So, what does the kernel do anyway? The kernel is a layer that sits between the hardware and the Standard C Library, it provides a layer that helps us interact with the hardware, peripheral devices, allocate memory, and so on. It also enforces privileges in order to tell if an operation is allowed or not. Some CPU instructions can only be done by the kernel, and not by any software that sits on top of it. Putting it in simple terms, the kernel is an abstraction layer on top of the hardware: an application calls a function in the Standard C Library, which calls the kernel, which interacts with the hardware.

Another important detail is that the kernel is a not a huge program, it has just 5Mb, and has only the essential pieces to support the Operating System. However, we can expand its functionalities through modules - Loadable Kernel Modules (LDMs). These modules are normally used by device drivers, they get "linked" into the kernel so they run in the same scope. We can add and remove modules in the kernel at runtime, and this is what I am going to do in this post.

Modules are built for a specific kernel version, and are conveniently installed in /lib/modules/[your kernel version]. If you use the command uname -r, you can get the name and version of your kernel.

Before I show you the module, some useful commands:

Useful commands
  • lsmod

Will give you a list of all loaded modules you have. This is one of the lines I got:

usbcore               208896  9 uvcvideo,usbhid,snd_usb_audio

These columsn are, respectively: name of the module, size, by how many things (I don't know exactly what they are) it is used, and its dependencies (there were more dependencies there, but I removed some)

  • modinfo

Will give you a detailed description of a module. This is the description for usbcore:

filename:       /lib/modules/4.11.9-1-ARCH/kernel/drivers/usb/core/usbcore.ko.gz
license:        GPL
alias:          usb:v*p*d*dc*dsc*dp*ic09isc*ip*in*
alias:          usb:v*p*d*dc09dsc*dp*ic*isc*ip*in*
alias:          usb:v05E3p*d*dc*dsc*dp*ic09isc*ip*in*
depends:        usb-common
intree:         Y
vermagic:       4.11.9-1-ARCH SMP preempt mod_unload modversions 
parm:           usbfs_snoop:true to log all usbfs traffic (bool)
parm:           usbfs_snoop_max:maximum number of bytes to print while snooping (uint)
parm:           usbfs_memory_mb:maximum MB allowed for usbfs buffers (0 = no limit) (uint)
parm:           authorized_default:Default USB device authorization: 0 is not authorized,
                1 is authorized, -1 is authorized except for wireless USB (default,
                old behaviour (int)
parm:           blinkenlights:true to cycle leds on hubs (bool)
parm:           initial_descriptor_timeout:initial 64-byte descriptor request timeout in
                milliseconds (default 5000 - 5.0 seconds) (int)
parm:           old_scheme_first:start with the old device initialization scheme (bool)
parm:           use_both_schemes:try the other device initialization scheme if the first
                one fails (bool)
parm:           nousb:bool
parm:           autosuspend:default autosuspend delay (int)
  • insmod

Used to insert (load) modules from a path

  • rmmod

Used to remove modules

  • modprobe

Used to load/unload modules. It is more powerful than insmod and rmmod, and can handle dependencies

Making the module

A module is nothing more than a C program. It basically starts as two functions: one for initialising, and one for exiting (in case you have some cleanup to do).

#include <linux/init.h>
#include <linux/module.h>

int initmodule(void)
{
  return 0;
}

void exitmodule(void)
{

}

module_init(initmodule);
module_exit(exitmodule);

This is the basic structure. Notice how we are importing two headers: linux/init.h and linux/module.h. These are headers that will give us some parts of the API to use in the kernel! Another interesting thing is: why am I not importing stdio.h? Well, this is because we are in a different layer: this program will not sit on top of the kernel and the Standard C Library - it is executed WITH the kernel, so there is no stdio.h!

Alright. I'm going to add a little more stuff to this code now. How about some documentation about the module?

MODULE_AUTHOR("Henrique S. Coelho");
MODULE_DESCRIPTION("A completely useless kernel module");
MODULE_LICENSE("GPL");

It would also be nice to add some functionality to it. How about this:

  • The module will ask your name, and will greet you like Hello Joe!; if no name is provided we will assume the name "there" so it will be displayed as Hello there! - hacky, I know! I love it.
  • The module will ask how many times you want this message to be printed. The default will be 5
  • The module will ask if it should say "goodbye" when it exits. The default is true

These options will be passed as arguments to the module.

// Default arguments
static int   repeats = 5;
static char  *name   = "there";
static bool  saybye  = true;

// default value, type, and permission
// S_IRUGO = value is read only
module_param(repeats, int,   S_IRUGO);
module_param(name,    charp, S_IRUGO);
module_param(saybye,  bool,  S_IRUGO);

After making the logic, this is how our module looks like:

// mymodule.c

#include <linux/init.h>
#include <linux/module.h>

static int   repeats = 5;
static char  *name   = "there";
static bool  saybye  = true;

// S_IRUGO = value is read only
module_param(repeats, int,   S_IRUGO);
module_param(name,    charp, S_IRUGO);
module_param(saybye,  bool,  S_IRUGO);

MODULE_AUTHOR("Henrique S. Coelho");
MODULE_DESCRIPTION("A completely useless kernel module");
MODULE_LICENSE("GPL");

int initmodule(void)
{
  unsigned short i;
  for (i = 0; i < repeats; i++)
    printk("Hello %s!\n", name);
  return 0;
}

void exitmodule(void)
{
  if (saybye)
    printk("Bye bye!\n");
}

module_init(initmodule);
module_exit(exitmodule);

Another detail you may have noticed: what is printk? This is a function used by the kernel to print messages (no, no printf here). These messages will be directed to the buffer of the kernel.

Awesome! Now, how do we compile this?

To compile this thing, we will make a Makefile in this directory. It should contain this line:

obj-m := mymodule.o

mymodule.o is the name of my module (conveniently called mymodule.c) after it is compiled.

We will not run this Makefile - the kernel will. We will use a Makefile from the kernel, which will use this Makefile to compile the module. Confusing? Yes, it is.

This is how we call the Makefile of the kernel:

$ make -C /lib/modules/`uname -r`/build M=$(PWD) modules

Some explanation:

  • -C /lib/modules/uname -r/build Tells make where the Makefile is. The makefile for the kernel is located in /lib/modules/[my kernel version]/build - I used the command uname -r as a shortcut to get the name and version of my kernel
  • -M=$(PWD) Tells make where to build the module. In this case: in my current directory
  • modules Tells which section of the Makefile to execute (remember make install?). We are telling make to make a module

So, again: we are executing the kernel's Makefile, which will execute the Makefile of our project.

Now, I like to automate things, so I made this Makefile instead:

all: mymodule.c
    make -C /lib/modules/`uname -r`/build M=$(PWD) modules

clean:
    make -C /lib/modules/`uname -r`/build M=$(PWD) clean

obj-m := mymodule.o

Nice. I can just call make and it builds the module. make clean will clean the directory.

After running it, I get this lovely output:

make -C /lib/modules/`uname -r`/build M=/home/hscasn/Desktop/kmodule modules
make[1]: Entering directory '/usr/lib/modules/4.11.9-1-ARCH/build'
  CC [M]  /home/hscasn/Desktop/kmodule/mymodule.o
  Building modules, stage 2.
  MODPOST 1 modules
  LD [M]  /home/hscasn/Desktop/kmodule/mymodule.ko
make[1]: Leaving directory '/usr/lib/modules/4.11.9-1-ARCH/build'

No erros, unlike this sentence!

Cool. So, we got a .ko file. This stands for Kernel Object, and this is our module.

Before I execute it, I will open a terminal and type the following command:

$ dmesg -w

This command prints the message buffer from the kernel (our messages will be there). The -w option will make the command wait and print new lines as they come.

Now we can finally load the module:

$ sudo insmod ./mymodule.ko

Immediately, this pops up in my other terminal (with dmesg running):

[23430.693089] Hello there!
[23430.693090] Hello there!
[23430.693090] Hello there!
[23430.693090] Hello there!
[23430.693091] Hello there!

It is alive! Let's try unloading the module:

~/kmodule $ sudo rmmod mymodule

Result:

[23434.868493] Bye bye!

Now I will try with other arguments: this time my name will be "Joe", I want the message to be printed one time, and I do not want the module to say goodbye:

~/kmodule $ sudo insmod ./mymodule.ko repeats=1 name=Joe saybye=0
~/kmodule $ sudo rmmod mymodule

Output:

[23448.108956] Hello Joe!

Again. This time, my name will be David, I want the message to be repeated 3 times, and I want a goodbye message:

~/kmodule $ sudo sudo insmod ./mymodule.ko name=David repeats=3 saybye=1
~/kmodule $ sudo rmmod mymodule

And the output:

[23468.661605] Hello David!
[23468.661605] Hello David!
[23468.661605] Hello David!
[23472.709457] Bye bye!

Today was a good day.