Sunday, 8 February 2009, 12:02

Developing on Linux, Part 3: Writing Kernel Modules

On this phase, already the third article about writing drivers for Linux you will write your first module and insert it into the source tree of the kernel. I admit that it sounds high-profile, but it has no actual magic, and I would say it is fairly simple. Let's gets our hands dirty!

Linux 2.6.x Module Structure

The good thing about writing modules for the Linux 2.6 kernel is that they all have a standard structure that has to be accomplished, thus guiding the programmer at any moment. The typical structure of a kernel module can be depicted from the code below:

/*
 * module_template.c
 */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

int init_module()
{
    /* do something on initialization */

    return 0;
}

void clean_module()
{
    /* do something on module unloading */
}

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Module description here");
MODULE_AUTHOR("Module author here");

Needless to say, normal functions can be created inside the source of the module and they shall be called from init_module or clean_module.

An important thing to remember is that, when developing drivers on the kernel level, the standard library functions are not available. That is, malloc, printf and similar are replaced by more primitive functions offered by the kernel, such as kmalloc or printk. This happens with many functions available in the standard C library.

Hello World Module

So let's write a Hello, world! for the kernel. The code is shown below:

/*
 * hello_version.c
 */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

int init_module()
{
    printk(KERN_INFO "Hello, world!\n");

    return 0;
}

void clean_module()
{
    printk(KERN_INFO "Goodbye, cruel world!\n");
}

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Greeting module");
MODULE_AUTHOR("Claudio M. Camacho");

Now, in order to compile it, we just need a simple Makefile, which may look like this:

obj-m := hello_version.o

KDIR := /usr/src/linux
PWD := $(shell pwd)

default:
	$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules

Assuming that your module has been written in a file named hello_version.c, the above-provided Makefile will generate the necessary object (.ko file) so that it can be loaded against the kernel. Please note that the KDIR path should be set to the directory where your kernel sources are residing.

Once you type make, a hello_version.ko kernel object file will be generated. Now you can load it using insmod hello_version.ko and remove it using rmmod hello_version. If everything goes right, you should see the following messages on your console (or terminal):

~$ sudo insmod hello_version.ko
Hello, world!
~$ sudo rmmod hello_version
Goodbye, cruel world!

Accordingly, you may try the lsmod command in order to check that your kernel module has been loaded successfully.

Module Parameters

For this example, you will create a module that can accept parameters. Don't worry, the kernel architecture has been designed so that you do not have to deal with much trouble when adding parameters to your driver modules. Consider the following module code:

/*
 * test_driver.c
 */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

/* parameters */
static char *whom = "Claudio";
module_param(whom, charp, 0);

static int howmany = 1;
module_param(howmany, int, 0);

int init_module()
{
    int i = 0;

    for (i = 0; i < howmany; i++)
        printk(KERN_ALERT "(%d) Hello, %s\n", i, whom);

    return 0;
}

/* module exit */
void cleanup_module(void)
{
    printk(KERN_ALERT "Goodbye cruel %s!\n", whom);
}

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Greeting module");
MODULE_AUTHOR("Claudio M. Camacho");

As simple as that, now you can load your module passing some parameters. As you can also see, the parameters have some default value (Claudio and 1), which will be taken in account if no parameters are specified on the command line when loading the module. In order to test the module parameters you have to load it issuing its parameters with some values:

~$ sudo insmod test_driver.ko howmany=3 whom=Antonio
Hello, Antonio
Hello, Antonio
Hello, Antonio

In this case, if you specify 3 as howmany, the loop in init_module will be run three times, thus printing three times the greeting message. Please realize that this is only a simple example, but that is the way the kernel modules work when being loaded with parameters.

Adding a Driver to the Kernel Sources

Finally, now that you have probably got the idea of the modules and how do they work for the Linux kernel, it would be nice to create an useful driver and integrate it into the kernel sources. In fact, it would be nice to see the driver in the kernel configuration interfaces, with its help, its dependencies and everything else, like any other driver in the kernel.

Before adding the driver to the kernel, let's write a third example. A more complicated driver that does something else than just greeting people. For this example, you will write a driver that creates an entry in the /rpoc directory (procfs) and shows information to the userland.

One nice example would be to count the number of seconds that have elapsed since the kernel module was loaded into the kernel memory. So, for instance, an user could issue cat /proc/elapsed and see how many seconds have elapsed from the moment the module was loaded. The module code is below:

/*
 *  proc_test_driver.c
 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>

#define procfs_name "elapsed"

struct proc_dir_entry *Our_Proc_File;
struct timeval start;
struct timeval end;

int procfile_read(char *buffer,
                  char **buffer_location,
                  off_t offset, int buffer_length, int *eof, void *data)
{
    int ret;

    /* check the time */
    do_gettimeofday(&end);

    /* this is for syscalls like read(), that will continue issuing reading */
    if (offset > 0)
    {
        /* we have finished to read, return 0 */
        ret  = 0;
    } else {
        /* fill the buffer, return the buffer size */
        ret = sprintf(buffer, "%lds\n", end.tv_sec - start.tv_sec);
    }

    return ret;
}

int init_module()
{
    /* check the time */
    do_gettimeofday(&start);

    /* create the file under /proc */
    Our_Proc_File = create_proc_entry(procfs_name, 0644, NULL);

    /* check for errors con creation */
    if (Our_Proc_File == NULL)
    {
        remove_proc_entry(procfs_name, NULL);
        printk(KERN_ALERT "Error: Could not initialize /proc/%s\n",
               procfs_name);
               return -ENOMEM;
    }

    /* configure reading on the proc file */
    Our_Proc_File->read_proc = procfile_read;
    Our_Proc_File->owner     = THIS_MODULE;
    Our_Proc_File->mode      = S_IFREG | S_IRUGO;
    Our_Proc_File->uid       = 0;
    Our_Proc_File->gid       = 0;
    Our_Proc_File->size      = 37;

    return 0;
}

void cleanup_module()
{
    remove_proc_entry(procfs_name, NULL);
}

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("/proc test driver");
MODULE_AUTHOR("Claudio M. Camacho");

Before inserting this driver into the kernel mainline, let's analyze what it does. First, as you need to use the /proc filesystem, you need to include the proc_fs.h header. The name of the file under /proc is defined as procfs_name.

init_module checks the time (this is the time when the module was loaded). Please note that there is no gettimeofday but do_gettimeofday instead, which is a kernel-level equivalent for gettimeofday(). Then the module creates the proc file entry and configures it. The configuration is a manual tuning of the proc file structure.

The most important parts of configuring the proc file entry are the read_proc symbol and the size. The former is a pointer to the function that fills the buffer for creating a text interface to display when reading the file under /proc, and the latter is the maximum file size on the proc entry. Accordingly, any time the userland tries to read this file entry, the function pointed by read_proc will be issued, in order to fill the buffer with text, to be returned to the userland process that is reading.

In addition, it is important to remove any entry that has been created in the /proc when the module is unloaded. This is critical, since not removing it makes the system unstable and the /proc directoy would not work anymore, returning a Segmentation fault since a pointer to the kernel memory is missing.

Finally, the function procfile_read is the most difficult part in this module. This is the callback (or hook) to fill the buffer for the userland programs. As a suggestion, if you do not intend to develop a very professional driver using the /proc filesystem, it is enough if you implement a similar function in your code any time you want to let the userland read from your proc entry. The vague documentation about procfs makes it difficult to invent different approaches, and so it is advisable to use conventional callbacks seen in the examples available on the Internet.

So, at last, you have been waiting this moment, and now it is time to insert your new driver into the kernel sources. For that purpose, you will clone you kernel sources directory and apply some modifications there:

~$ cd /usr/src
~$ sudo rsync -a linux-2.6.28.4 linux-2.6.28.4-patched
~$ cd linux-2.6.28.4-patched
~$ sudo cp ~/proc_test_driver.c drivers/misc/
~$ cd drivers/misc/
~$ sudo echo "obj-$(CONFIG_PROC_TEST_DRIVER)  += proc_test_driver.o" >> Makefile

Now you should edit the Kconfig file under drivers/misc in order to add you driver to the kernel configuration interface. This will make your driver to appear in configuration interfaces such as menuconfig, xconfig, etcetera. You should add something similar to the lines below:

config PROC_TEST_DRIVER
	tristate "/proc Test Driver"
	depends on X86
	---help---
	 This driver adds support for testing a simple dummy driver on the /proc filesystem.

After this is done, you kernel is ready to compile your new driver. If you go to the kernel sources root directory and issue make menuconfig, going into the Device Drivers, Misc devices, you should see your driver and the corresponding help and everything, just like a normal driver. Furthermore, try to enable it a compile your kernel. If you build it as a module, you can then load it and unload it, testing the file /proc/elapsed as we discussed previously. On the other hand, if you make the driver built-in, then the file /proc/elapsed should be permanent.

Last, it is possible to create a patch for the vanilla kernel sources. This is quite useful, since you can add your driver to the kernel sources by sending a signed patch to Linus Torvalds. This won't be the case, but let's just create a patch and see that it actually works:

~$ cd /usr/src
~$ sudo diff -Nurp linux-2.6.28.4/ linux-2.6.28.4-patched/ > proc_test_driver.patch
~$ cd linux-2.6.28.4
~$ cat ../proc_test_driver.patch | sudo patch -p1
patching file drivers/misc/Kconfig                                       
patching file drivers/misc/Makefile                                      
patching file drivers/misc/proc_test_driver.c

Afterwards, your vanilla kernel should now contain your new driver. You can menuconfig it and compile, and you will see that your driver is actually compiled ant it works. Great! Isn't it?

References

The Linux Kernel Module Programming Guide [online]. Peter Jay Salzman, 2001.
URL: http://tldp.org/LDP/lkmpg/2.6/html/
Accessed 8th February 2009

Sunday, 1 February 2009, 11:02

Developing on Linux, Part 2: Kernel Compilation

In the first part of these articles you learned how to download the kernel sources and apply some patches. Besides you do know now how to search for a certain information in the kernel sources as well. In this part, you are going to learn how to configure a kernel, how to compile it for a given architecture and, what is more important, how to cross-compile and test it from one architecture to another.

Kernel Configuration

In order to configure the kernel, you must issue some commands to get a configuration menu where you can select which options will be enabled and which will not. For this lab exercise, you will focus on configuring a minimal kernel for an i386 computer and test it with Qemu.

So the first thing is to move into the kernel directory. Let's use a 2.6.28.2 kernel, which is quite new (as of writing this article). Please notice that you need certain packages to make the configuration menu appear, because it is generated on the fly, so you must install some packages such as ncurses-dev and the obvious gcc and make (in case you don't have them already).

~$ sudo apt-get install bzip2 gcc make ncurses-dev
~$ wget ftp://ftp.kernel.org/pub/linux/kernel/v2.6/linux-2.6.28.2.tar.bz2
~$ tar jxvf linux-2.6.28.2.tar.bz2
~$ cd linux-2.6.28.2
~$ make menuconfig

After typing make menuconfig on the console, you should get a curses-based menu where you can configure every option on the kernel. Each option is classified inside sub-menus, organized according to the type of devices, kernel subsystems, etcetera. Notice that on each option in the kernel configuration menu there is a Help button (on the bottom right), that you can access and you will get a brief explanation about that option and what it enables.

For a minimal i386 PC, you will have to disable almost anything, but leaving the processor type option for PC-compatible and i386 architecture. So now, please try to configure your kernel for the following architecture (which you will later test with a certain image for qemu):

  • PC i386 computer
  • IDE ATA hard drive
  • ext2 file system

Kernel Compilation

Now please take your time and go through every sub-menu in the kernel (it may take you more than an hour), reading carefully every help and disabling anything does it would not be critical, such as device drivers, USB and SCSI, etcetera. Just leave the basic processor configuration, the IDE ATA driver (generic one) and the ext2 file system module on the File Systems subsection. Once you have your configuration ready, it is time to compile the kernel. This can be done in the following way:

~$ make

Testing

Now, in order to test this kernel, instead of trying it out on your own machine (which could obviously not be an i386 PC), you will download some lab files from free-electrons:

~$ wget http://free-electrons.com/labs/embedded_linux.tar.bz2
~$ mkdir labs
~$ cd labs
~$ tar jxvf ../embedded_linux.tar.bz2

That tarball contains a set of tests that you can readily use with your lab exercises and you won't have to bother so much with you PC machine. So once you have compiled your kernel, the image of that kernel will be placed in linux-2.6.28.2/arch/x86/boot/bzImage. In order to test that the kernel is working, perform the following command (using Qemu):

~$ qemu -m 32 -kernel linux-2.6.28.2/arch/x86/boot/bzImage -hda data/linux_i386.img -boot c -append "root=/dev/hda"

If you configured your kernel correctly, you will see a kernel booting on the emulator screen and it will reach a point where it complains: Warning: unable to open an initial console.. Don't worry this is normal, since the image is ready to reach this point, and that will mean that you have correctly configured and compiled your kernel for a minimal i386 installation.

Cross-Compiling

Cross-compiling is the technique of compiling a given software for a certain architecture using tools which are compiled for a different architecture. That is, it is possible to compile a kernel, for instance, for the ARM architecture on a i386 machine, thanks to the cross-compiling toolchains.

For this exercise, please go into the embedded_linux/labs/linux/lab3 directory and download the 2.6.20 version of the Linux kernel and apply the existing patch in the data/ subdirectory.

~$ cd labs/linux/lab3
~$ wget ftp://ftp.kernel.org/pub/linux/kernel/v2.6/linux-2.6.20.tar.bz2
~$ tar jxvf linux-2.6.20.tar.bz2
~$ cd linux-2.6.20
~$ cat ../data/patch-arm-versatile-qemu-2.6.24 | patch -p1

Now it is time to install the ARM cross-compiling toolchain, which contains the necessary tools to generate the ARM kernel:

~$ sudo echo "deb http://free-electrons.com/labs/ubuntu/ ./" >> /etc/apt/sources.list
~$ sudo apt-get update
~$ sudo apt-get install buildroot-uclibc-arm-toolchain
~$ export PATH="/usr/local/uclibc-0.9.28-2/arm/bin/:$PATH"

Now that you have installed the toolchain and set up your PATH environment variable, you will have to modify the Makefile in order to proper tell the make system to use the cross-compiler, instead of the one used by default on your i386 architecture. Therefore, in the kernel's root directory's Makefile, you should set the following options:

ARCH ?= arm
CROSS_COMPILE ?= arm-linux-

Please note that you could also set those as environment variables before issuing make, which might be cleaner. Nevertheless, it is clear enough that you can also do it in the Makefile, for the sake of simplicity. You can also, before compiling, perform a make menuconfig and see which options are available, where there is a difference in the sub-menus available for x86 machines. Now let's build the kernel.

~$ make menuconfig
~$ make

Testing the Cross-Compiled Kernel

Now, in order to test if the kernel is working or not, you should create an initram file system (initramfs) with the directory rootfs provided in labs/linux/lab3/data/rootfs. Create an initramfs with that directory structure and then test with Qemu and the cross-compiled kernel image:

~$ mkdir initrd
~$ dd if=/dev/zero of=initrd.img bs=1k count=4096
~$ mkfs.ext2 -F initrd.img
~$ sudo mount -o loop initrd.img initrd/
~$ sudo rsync -a data/rootfs/ initrd/
~$ sudo umount initrd

Finally, you should have your initrd.img ready to be run with Qemu, so let's test the ARM kernel:

~$ qemu-system-arm -M versatilepb -m 16 -kernel linux-2.6.20/arch/arm/boot/zImage -append "clocksource=pit console=tty0 rw"

At this point, you should able to reach the command line (shell) on your ARM kernel, which means that you have been able to cross-compile the 2.6.20 kernel and run it on the ARM emulator. Congratulations!

Sunday, 25 January 2009, 10:01

Developing on Linux, Part 1: Kernel Sources

This post is just, I hope, one of several to come, explaining how to download the Linux kernel sources, modifying them yourself, and even creating modules for the that kernel. In fact, this post refers to a new course I recently started at Helsinki Metropolia University of Applied Sciences, named Device Drivers Development, whose goal is to teach students how to deal with modern 2.6.x kernels and how to develop device drivers for the kernel, at a basic level. Therefore, I will use that documentation given in class to include here the series of lab exercises that we are doing every week, so that all you can also get your hands on the Linux kernel and do something, hopefully, interesting.

This first lab consists of just getting to know how the Linux kernel is published online and how to get its sources, in order to be able to use them on our hard drive. But first, let me introduce some basic information about the kernel. Please refer to my article: Linux Kernel Overview to get the theory supplement before following this lab exercise.

So for this time, you will download the kernel sources, unpack them, patch them, and find some functionalities inside it. Let's check it out.

Getting the sources

First, visit the kernel website (http://www.kernel.org) and download the kernel version 2.6.22, which you may find in the subdirectory pub/linux/kernel/v2.6/.

You can download the kernel with one single command on the command line:

~$ wget http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.22.tar.bz2

The next step will be to unpack the sources, that you may do with the tar:

~$ tar jxvf linux-2.6.22.tar.bz2

The previous command will unpack the sources into the directory linux-2.6.22. These are the whole sources for the kernel version 2.6.22, but they can be easily upgraded to the next version by downloading incremental patches. This time, you will download the incremental patch to upgrade it to 2.6.23 and then another patch to upgrade it to the 2.6.23.17 version:

~$ wget http://www.kernel.org/pub/linux/kernel/v2.6/patch-2.6.23.bz2
~$ wget http://www.kernel.org/pub/linux/kernel/v2.6/patch-2.6.23.17.bz2
~$ wget http://www.kernel.org/pub/linux/kernel/v2.6/patch-2.6.23.bz2.sign
~$ wget http://www.kernel.org/pub/linux/kernel/v2.6/patch-2.6.23.17.bz2.sign

Applying patches

The first patch is applied against the 2.6.22 kernel, and it will upgrade the sources to the version 2.6.23. The second patch is applied against the 2.6.23 kernel, in order to update the minor version to .17. The last two files are a GPG signature for validating the authenticity of these patches. In order to apply any patch, you must first install the program patch:

~$ sudo apt-get install patch

Now move into the linux-2.6.22 directory and apply the first patch to upgrade to the 2.6.23. Then apply the patch-2.6.23.17.bz2 file in order to get the latest 2.6.23.x kernel:

~$ cd linux-2.6.22
~$ bzcat ../patch-2.6.23.bz2 | patch -p1
~$ bzcat ../patch-2.6.23.17.bz2 | patch -p1
~$ cd ..
~$ mv linux-2.6.22 linux-2.6.23.17

With the previous commands, you just upgraded the kernel from 2.6.22 to 2.6.23.17. However, you may first want to verify the authenticity of these patches, so you can do that with the following commands:

~$ gpg --keyserver pgp.mit.edu --recv-keys 0x517D0F0E
~$ gpg patch-2.6.23.bz2.sign
~$ gpg patch-2.6.23.17.bz2.sign

Checking the signature will tell if the patches come from the Linux Archives website or from another (otherwise) erroneous source. In addition, I didn't mention that we rename the directory from linux-2.6.22 to linux-2.6.23.17 just for being more informative and consistent, but the name of the directory could be anything.

Looking for something in the sources

Finally, you can start looking for a certain function or information inside the sources of the kernel. Please notice that the kernel sources are, more or less, well structured and different subdirectories separate different subsystems. Here are some examples of where things are placed:

  • Linux logo image: drivers/video/logo
  • maintainer of the 3C505 network driver: drivers/net/3c505.c
  • home page of the parallel port team: drivers/parport/Kconfig
  • declaration of platform_device_register(): include/linux/platform_device.h

Another way to find information inside the sources is to use the LXR website, which is user-friendly.

Conclusion

This is the first part. You just need to understand how to download the kernel sources and patch them, besides knowing how to find some basic information in them. The next lab session will be on configuring and compiling the kernel with your own options. Have fun!

 
© Claudio M. Camacho

Updated on Sunday, 29 August 2010 18:02