A helloworld kernel module

Today, I experimented with the linux kernel modules for the first time and I’ve written a simple module that prints a message (helloworld :P) every time that someone reads from the /dev/ktest (a custom character device) and counts how many times the device was opened for reading.

Here’s what I’ve done step-by-step:

  1. I made sure that the linux headers are installed. On Debian:
  2. I wrote a Makefile:

    The Makefile runs make in in /lib/modules/uname -r/build. This directory contains another Makefile which is used to actually compile my module.
  3. After the Makefile, I’ve written the code of the test module (ktest.c: https://eleni.mutantstargoat.com/hg/index.cgi/kernel-helloworld-test/file/dbbd63da261f/ktestmodule/ktest.c). Let’s examine it line by line:
    Every module starts by calling some macros. Apart from those for the license, author, description that obviously set the license, the author and the description, we need to call module_init and module_exit. The module_init macro’s parameter is a function pointer to a module initialization callback that will be called at the begining, whereas the module_exit macro’s parameter is a function pointer to a callback that will be called at the end. The callbacks are the ktest_ini and ktest_fini functions below.

    Let’s see the implementation:

    The initialization callback registers a character device (that we previously defined as DEV_NAME “/dev/ktest”) and prints information on success or failure.

    The exit callback unregisters the device.

    In order to perform operations on this device, we need to fill a struct of type file_operations:

    We don’t need to fill all the fields, but only those that interest us. The rest will be set to zero automatically. By populating .open, .release, .read we set the functions that are going to be called when we open, release and read the device, respectively.

    This function is used to tell the kernel which device we want to open (inode = the filesystem node). Since we only have one device file we can ignore the parameters for simplicity and call try_module_get(THIS_MODULE). This call is supposed to be unsafe in general but I didn’t care much since this is only a helloworld example and I wanted to keep things simple.

    Same here, I ignored the parameters and used module_put for simplicity.

    The function that is called when we read from the device is the following:

    This function takes as a parameter a pointer to the device, a pointer to a userspace buffer and the buffer size and offset. What I’ve done here was to create a kernelbuffer (kbuf) in the size of the userspace buffer, fill it with some text, copy it to the userspace buffer (to pass it to the user program) and free the kernel buffer.

  4. Next step was to insert the module to the kernel and test it works.
    I created a device /dev/ktest first:

    There might be a better way to create devices using for example udev when the module is loaded or something else but I didn’t know how to do it in a generic way that works everywhere, so, I just called mknod to create the device quickly. :-p

    To insert the ktest module to the kernel:

    after running make. I used dmesg to see if the ktest device was registered, the message was something like:

    [524178.076365] Registered character device. Name: ktest number: 243.

    To test if the module works, I initially used cat but then I’ve written a short program myself to read the output once every time (cat reads from the device continuously):

    By running it I was able to see the helloworld output. To remove the module I ran:

Test code:


Leave a Reply

Your email address will not be published. Required fields are marked *