6 minute read

Track Time in Your Driver with Kernel Timers

This article shows how, with the help of kernel timers, resource allocation and the time-bound operation of various devices occurs without any conflicts.

Kernel timers offer a great way to keep track of time in device drivers. Instead of implementing a loop to wait for some time to expire, a timer function can provide a one-step solution. Timers are also used to poll a device at a constant interval, when the hardware can't fire an interrupt.

Advertisement

The kernel measures time by counting the number of ticks since the time the system has booted, and stores this count in a global jiffies variable. The define HZ controls the rate of ticks and can be modified by the user in <asm/param.h>.

A kernel timer is a data structure that allows the kernel to invoke a user-defined function, with user-defined arguments and user-defined time. This is defined in linux/timer.h and kernel/timer.h. In recent kernels, there are two ways to implement timers. The first is the timer API, which is used in most places, but it is less accurate and quite simple. The other is the high-resolution timer API, which allows us to define time in nanoseconds.

A simple timer API

The timers are implemented as a doubly linked list; the structure is shown below:

struct timer_list { struct timer_list *next; struct timer_list *prev; unsigned long expires;

void (*function)(unsigned long); unsigned long data;

int slack; };

The kernel provides a driver with helper functions to declare, register and remove kernel timers. You will never

have to touch (and you must not) the *next and *prev pointers. The kernel handles this. Thus, from a user point of view, the timer_list provides an expiration duration, a callback function and user-provided context. The expires field represents the jiffies value when the timer is expected to run. The user can initialise the timer by simply calling setup_timer, which sets up the callback and user-provided context. Also, the user can set the values of data and function in the timer and call init_timer (which is called internally by setup_timer). The prototypes are given below:

void init_timer( struct timer_list *timer ); void setup_timer( struct timer_list *timer, void (*function) (unsigned long), unsigned long data );

The next step is to set the expiration time. This can be done by calling mod_timer. Timers are automatically deleted upon expiration, but to leave it hanging after the module has been unloaded may cause the kernel to hang. This also means that you can add the timer again from the handler itself.

The cleanup_module function calls timer_pending to detect whether the timer has expired or not. It returns true if the timer is pending. If you want to extend the timeout to acquire the following behaviour, issue the following commands:

del_timer(&timer1); timer1.expires=jiffies+new_value; add_timer(&timer1);

You can do this by using mod_timer(&timer1, new_ value); if the timer is expired by the time mod_timer is called, it acts like add_timer and adds the timer again.

Implementing timer as an LKM

LKM (Loaded Kernel Module) is supported by many UNIXlike operating systems and Microsoft Windows; it is an object file that is used for extending the kernel to support new hardware. Let's try to make a module that implements timer functions, to show you how to use timers in practice. For you to try this, you must have the kernel headers package installed. I have Fedora 17 installed, so I used su -c 'yum install kernel-devel' to install the specific headers. You may need to install 'kernel-PAE-devel' if you are using the PAE kernel. Debian and Ubuntu users may use the apt-get install module-assistant to install the packages necessary to build an LKM.

After you have finished installing the packages, consider the following program:

#include<linux/kernel.h> #include<linux/module.h> Figure 1: Compiling the timer_example.c

Figure 2: Timer module running

Figure 3: Timer module removed

#include<linux/timer.h>

/*create a timer named timer1*/ static struct timer_list timer1;

/*called when timer expires*/ void timer1_expires(unsigned long data) { printk("timer1 called back (%ld)\n", jiffies); }

int init_module(void) { int retval; printk("Setting up timer for initialisation \n"); setup_timer(&timer1, timer1_expires,0);

printk("Starting timer to fire in 200ms (%ld)\n", jiffies); retval=mod_timer( &timer1, jiffies + msecs_to_jiffies(200) ); if(retval) printk("Error in mod_timer\n"); return 0; }

void cleanup_module(void) { int retval=del_timer(&timer1);;

if(retval) printk("Timer still in use\n");

printk("Timer removed"); return; }

Within init_module, setup_timer initialises the timer and sets it off by calling mod_timer. When the timer expires, timer1_expires is called. When the module is removed, the timer is deleted by invoking del_timer. Save this in a directory of your choice and name the file timer_example.c. The next step will be to create a Makefile in the same directory, as shown below:

obj-m += timer_example.o KDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd)

all: $(MAKE) -C $(KDIR) M=$(PWD) modules

clean: $(MAKE) -C $(KDIR) M=$(PWD) clean

Now, in your terminal, as the root, go to the source code directory and run make to compile the code (see Figure 1). After compiling, you will find the output timer_example.ko file, which has to be inserted into the kernel, with insmod timer_example.ko. Since there are no error messages, how does one verify that the module has been inserted? Check the dmesg output with dmesg | tail (Figure 2). Now remove the module (rmmod timer_example) and the timer will also be removed, as you can see with another dmesg | tail (Figure 3). To get rid of the messages with warnings, include a macro MODULE_LICENSE() with the string ‘GPL’ included in brackets as a parameter.

High-resolution timer API

The simple API discussed above is easy to implement, but is not accurate enough to be used for real-time applications. The high-resolution timer API (‘hrtimer’) has almost the same structure (below) as the simple API, but some changes make it work with great accuracy. The global jiffies is not used, but a special data type ktime is used to represent time. This structure defines timers from a user perspective. This is implied by _softexpires (the expiry time given when the timer was armed), which gives the absolute earliest expiry time of the hrtimer. The function member is the same as before; start_pid is an important field—it stores the PID of the task that started the timer, and start_comm stores the name of the process that started the timer.

struct hrtimer { struct timerqueue_node node; ktime_t _softexpires; enum hrtimer_restart (*function)(struct hrtimer *); struct hrtimer_clock_base *base; unsigned long state; #ifdef CONFIG_TIMER_STATS int start_pid; void *start_site; char start_comm[16]; #endif };

The hrtimer structure must be initialised by hrtimer_ init(), and then it can be started with hrtimer_start. This call includes the expiration time (in ktime_t) and the mode of the time value (absolute or relative value). The prototypes are:

void hrtimer_init( struct hrtimer *time, clockid_t which_clock, enum hrtimer_mode mode ); int hrtimer_start(struct hrtimer *timer, ktime_t time, const enum hrtimer_mode mode);

To cancel a timer, use hrtimer_cancel or hrtimer_ try_to_cancel. The first will cancel the timer, but if the timer has already fired, it'll wait for the callback function to finish, and then cancel the timer. The latter will return false if the timer has already fired. Use this as per your needs.

extern int hrtimer_cancel(struct hrtimer *timer); extern int hrtimer_try_to_cancel(struct hrtimer *timer);

There are even more timer operations, which can be found in include/linux/hrtimer.h.

Some points to take care of

Kernel timers are run as a result of software interrupts. The timer function must be atomic in all cases. Kernel timers can cause serious race conditions, even on uni-processor systems, since they are asynchronous with other code. So, any piece of code used by the timer callback function must be protected from concurrent access, either from atomic types, or by using spin-locks. Last, but not least, a timer should run on the same CPU that registers it.

References

[1] http://en.wikipedia.org/wiki/Kernel_(computing) [2] http://fedoraproject.org/wiki/Building_a_custom_kernel [3] http://www.kernel.org/doc/Documentation/timers/hrtimers.txt

By: Rahul Gupta

The author graduated from BVCOE, New Delhi, and is fascinated with Web 2.0. He loves to play around with Linux and will be happy to receive any queries or suggestions at rahulgupta172@yahoo.com.

This article is from: