How to Implement Multiple Periodic Timers in Linux Kernel

Here we’ll see how to implement a timer module in Linux kernel that will allow users to create multiple periodic timers at the same time. Kernel already has a timer module that allows us to create multiple timers but the timers are not periodic. Using that module we get timeout signal (callback function) only once, after expiry of first timeout. We’ll create a wrapper on top that module and make the timer periodic.

You can also make the kernel timer module periodic without wrapper.

Read also: Implement periodic timer in Linux user space.

In our timer module we’ll maintain a timer list and configure the kernel timer module with one call back function for all timers. In the timer list we’ll save timer related information like callback function pointer, timeout value etc for each timer. When the kernel timer module will call our callback function, we’ll restart the timer and then call the user’s call back function corresponding to the timer. This way the user callback function will periodically be called based on the timeout value. Please note that this is not the only or best way of implementing periodic timer. Here it is assumed that you know the basic of kernel programming.

Programming Interface of the Periodic Timer

Let us first define the programming interface of our module in terms of data structure and function prototypes.

typedef void (*TTimerCallback)(unsigned long);

int TimerInitialize(void);
int TimerFinalize(void);
int TimerStart(unsigned long timer_id, TTimerCallback callback, unsigned long interval, unsigned long user_data);
int TimerStop(unsigned long timer_id);
int TimerModifyInterval(unsigned long timer_id, unsigned long new_interval);

TTimerCallback: This is a typedef of a function pointer. User of this module will have to define a function of that type and the address of the function should be passed in the TimerStart() function. After timeout expiry the function will be called.

TimerInitialize(): This function needs to be called only once to initialize the module.
TimerFinalize(): This function will be called if the timer module is no longer required in the program. It will stop all running timers and cleanup all intermediate data structures.
TimerStart(): This function will be used to register and start a timer. Caller of this function will pass a timer_id, that will be used in the future function calls to refer this particular timer. Callback function and the timeout value will also be passed to this function.
TimerStop(): This function will be used to stop and de-register a timer specified by the timer_id.
TimerModifyInterval(): This function will be used to change to timeout value of an active timer.

Implementation of the Periodic Timer

In this section, we will see how to implement the above mentioned functions. Before that, we’ll see the data structures that will be required internally.

LIST_HEAD( g_timer_list );

typedef struct STimerList
{
    unsigned long       m_ulTimerId;
    unsigned long       m_ulData;
    TTimerCallback      m_callback;
    unsigned long       m_interval;
    struct timer_list   m_timer;
    struct list_head    m_head;
} TTimerList;

Here we have one global list g_timer_list to hold all active timers and a structure TTimerList to hold information of a particular timer. Now we’ll the individual function implementation.

TimerInitialize():

int TimerInitialize(void)
{
    return 1;
}

In this example we don’t need to do anything. For completeness we have this function.

TimerFinalize():

int TimerFinalize(void)
{
    struct list_head *pos = NULL;
    struct list_head *pos1 = NULL;
    TTimerList *pTimer;
    int ret;

    list_for_each_safe( pos, pos1, &g_timer_list )
    {
        pTimer = list_entry( pos, TTimerList, m_head );

        if (pTimer)
        {
            
            /*Stopping the timer...*/
            ret = del_timer( &(pTimer->m_timer) );
            if (ret)
            {
                /*TODO The timer is still in use. What to do?*/
            }
            /*delete the timer from g_timer_list*/
            list_del(pos);
            kfree(pTimer);
        }
    }

    return 1;
}

In this function we traverse through the global list g_timer_list to stop all the active timers. In line number 16, we deleted them from the kernel. In line 17, we removed the timer from our internal list and in line 18, we freed the memory that was allocated for the timer.

TimerStart():

int TimerStart(unsigned long timer_id, TTimerCallback callback, unsigned long interval, unsigned long user_data)
{
    TTimerList *pTimer = NULL;

    /*Check whether the timer_id is already present.*/
    if (TIsTimerExists(timer_id))
    {
         pr_info("Timer (Id: %ld) already exists and running.n", timer_id);
         return 0;
    }
 
    pTimer = (TTimerList *)kmalloc(sizeof(TTimerList), GFP_ATOMIC);

    if (pTimer == NULL)
    {
        pr_info("Memory allocation failed for timer Id .n");
        return 0;
    }

    pTimer->m_ulTimerId = timer_id;
    pTimer->m_ulData    = user_data;
    pTimer->m_callback  = callback;
    pTimer->m_interval  = interval;

    /*Insert the timer instance to the g_timer_list*/
    list_add( &pTimer->m_head, &g_timer_list );

    /*Starting the timer.*/
    setup_timer(&(pTimer->m_timer), _CommonCallback, (unsigned long)pTimer);
    mod_timer( &(pTimer->m_timer), jiffies + msecs_to_jiffies(interval));

    return 1;
}

First we checked whether the timer specified by the timer_id is already running in the kernel. If so, we returned 0 indicating failure. Then allocated memory for a variable of type TTimerList to store all the information related to the timer. After that we added this timer to our internal list, g_timer_list. Finnally, we set the timer in the kernel with a common callback function _CommonCallback(). We’ll see the implementation of _CommonCallback() later. The important thing that you should notice is we passed the pointer of our timer structure, pTimer, to the setup_timer() function. This pointer will come when the _CommonCallback() will be called from the kernel timer module. We set the timeout value in line 30. On successful completion of the function returns 1.

TimerStop():

int TimerStop(unsigned long timer_id)
{
    int ret;
    TTimerList * pTimer = NULL;
    pTimer = _FindTimer(timer_id);

    if (pTimer == NULL) return 0;

    /*Stopping the timer...*/
    ret = del_timer( &(pTimer->m_timer) ); /*del_timer of an inactive timer returns 0, del_timer of an active timer returns 1*/
    if (ret != 1) return 0; /*The timer is still in use.*/

    /*delete the timer from g_timer_list*/
    list_del(&(pTimer->m_head));
    kfree(pTimer);
    
    return 1;
}

In this function, we first find out the internal structure pointer for the time from the timer_id, then we de-registered that from kernel. We deleted the timer from our internal list and freed the memory.

TimerModifyInterval():

int TimerModifyInterval(unsigned long timer_id, unsigned long new_interval)
{
    TTimerList * pTimer = NULL;
    pTimer = _FindTimer(timer_id);

    if (pTimer == NULL) return 0;

    pTimer->m_interval = new_interval;

    mod_timer( &(pTimer->m_timer), jiffies + msecs_to_jiffies(pTimer->m_interval));

    return 1;
}

In this function we simply modify the timeout interval of the timer.

_CommonCallback():

void _CommonCallback(unsigned long data)
{
    TTimerList *pTimer = (TTimerList *)data;

    if (pTimer)
    {
        /*Restarting the timer...*/
        mod_timer( &(pTimer->m_timer), jiffies + msecs_to_jiffies(pTimer->m_interval));

        /*Calling the user callback...*/
        pTimer->m_callback(pTimer->m_ulData);
    }
}

After timeout of any timer, this callback function will be called. We passed the same function for all timers to setup_timer() function in our TimerStart() function. From data we’ll get our TTimerList structure pointer as we passed that in setup_timer() function. Immediately we restarted the timer and called the user’s callback function that we saved in m_callback variable.

If you want to use the code here is the complete header and source file.

KernelTimer.h:

#ifndef __KERNEL_TIMER_H__
#define __KERNEL_TIMER_H__

#include <linux/timer.h>
#include <linux/list.h>

/*All timers are periodic.*/

typedef void (*TTimerCallback)(unsigned long);

/*
 * Note: Timer ID is set by the caller. This id needs to be unique across all calls of the
 * TimerStart function. On success the function returns 1 otherwise 0.
 */
 
int TimerInitialize(void);
int TimerFinalize(void);

int TimerStart(unsigned long timer_id, TTimerCallback callback, unsigned long interval, unsigned long user_data);
int TimerStop(unsigned long timer_id);
int TimerReset(unsigned long timer_id);
int TimerModifyInterval(unsigned long timer_id, unsigned long new_interval);

int TIsTimerExists(unsigned long timer_id);

typedef struct STimerList
{
    unsigned long       m_ulTimerId;
    unsigned long       m_ulData;
    TTimerCallback      m_callback;
    unsigned long       m_interval;
    struct timer_list   m_timer;
    struct list_head    m_head;
} TTimerList;
#endif

KernelTimer.c:

#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include "KernelTimer.h"

LIST_HEAD( g_timer_list );

static void _CommonCallback(unsigned long data);
static TTimerList *_FindTimer(unsigned long timer_id);

int TimerInitialize(void)
{
    return 1;
}

int TimerFinalize(void)
{
    struct list_head *pos = NULL;
    struct list_head *pos1 = NULL;
    TTimerList *pTimer;
    int ret;

    list_for_each_safe( pos, pos1, &g_timer_list )
    {
        pTimer = list_entry( pos, TTimerList, m_head );

        if (pTimer)
        {
            
            /*Stopping the timer...*/
            ret = del_timer( &(pTimer->m_timer) );
            if (ret)
            {
                /*TODO The timer is still in use. What to do?*/
            }
            /*delete the timer from g_timer_list*/
            list_del(pos);
            kfree(pTimer);
        }
    }

    return 1;
}

int TimerStart(unsigned long timer_id, TTimerCallback callback, unsigned long interval, unsigned long user_data)
{
    TTimerList *pTimer = NULL;

    /*Check whether the timer_id is already present.*/
    if (TIsTimerExists(timer_id))
    {
         pr_info("Timer (Id: %ld) already exists and running.n", timer_id);
         return 0;
    }
 
    pTimer = (TTimerList *)kmalloc(sizeof(TTimerList), GFP_ATOMIC);

    if (pTimer == NULL)
    {
        pr_info("Memory allocation failed for timer Id .n");
        return 0;
    }

    pTimer->m_ulTimerId = timer_id;
    pTimer->m_ulData    = user_data;
    pTimer->m_callback  = callback;
    pTimer->m_interval  = interval;

    /*Insert the timer instance to the g_timer_list*/
    list_add( &pTimer->m_head, &g_timer_list );

    /*Starting the timer.*/
    setup_timer(&(pTimer->m_timer), _CommonCallback, (unsigned long)pTimer);
    mod_timer( &(pTimer->m_timer), jiffies + msecs_to_jiffies(interval));

    return 1;
}

int TimerStop(unsigned long timer_id)
{
    int ret;
    TTimerList * pTimer = NULL;
    pTimer = _FindTimer(timer_id);

    if (pTimer == NULL) return 0;

    /*Stopping the timer...*/
    ret = del_timer( &(pTimer->m_timer) ); /*del_timer of an inactive timer returns 0, del_timer of an active timer returns 1*/
    if (ret != 1) return 0; /*The timer is still in use.*/

    /*delete the timer from g_timer_list*/
    list_del(&(pTimer->m_head));
    kfree(pTimer);
    
    return 1;
}

int TimerReset(unsigned long timer_id)
{
    TTimerList * pTimer = NULL;
    pTimer = _FindTimer(timer_id);

    if (pTimer == NULL) return 0;

    mod_timer( &(pTimer->m_timer), jiffies + msecs_to_jiffies(pTimer->m_interval));

    return 1;
}

int TimerModifyInterval(unsigned long timer_id, unsigned long new_interval)
{
    TTimerList * pTimer = NULL;
    pTimer = _FindTimer(timer_id);

    if (pTimer == NULL) return 0;

    pTimer->m_interval = new_interval;

    mod_timer( &(pTimer->m_timer), jiffies + msecs_to_jiffies(pTimer->m_interval));

    return 1;
}

int TIsTimerExists(unsigned long timer_id)
{
    struct list_head *pos = NULL;
    TTimerList *pTimer;

    list_for_each( pos, &g_timer_list )
    {
        pTimer = list_entry( pos, TTimerList, m_head );

        if (pTimer)
        {
            if (pTimer->m_ulTimerId == timer_id) return 1;
        }
    }
    return 0;
}

static TTimerList *_FindTimer(unsigned long timer_id)
{
    struct list_head *pos = NULL;
    TTimerList *pTimer;

    list_for_each( pos, &g_timer_list )
    {
        pTimer = list_entry( pos, TTimerList, m_head );

        if (pTimer)
        {
            if (pTimer->m_ulTimerId == timer_id) return pTimer;
        }
    }
    return NULL;
}

void _CommonCallback(unsigned long data)
{
    TTimerList *pTimer = (TTimerList *)data;

    if (pTimer)
    {
        /*Restarting the timer...*/
        mod_timer( &(pTimer->m_timer), jiffies + msecs_to_jiffies(pTimer->m_interval));

        /*Calling the user callback...*/
        pTimer->m_callback(pTimer->m_ulData);
    }
}

Author: Srikanta

I write here to help the readers learn and understand computer programing, algorithms, networking, OS concepts etc. in a simple way. I have 20 years of working experience in computer networking and industrial automation.


If you also want to contribute, click here.

2 thoughts on “How to Implement Multiple Periodic Timers in Linux Kernel”

  1. Hey nice article !

    But why the use of _CommonCallback ?
    Can we not use the user callback in the StartFunction() instead of _CommonCallback ?

    Thanks,

    1. You are right, the user callback could be assigned in place of _CommonCallback in this line.
      setup_timer(&(pTimer->m_timer), _CommonCallback, (unsigned long)pTimer);
      The user callback could directly be called.

      But that callback would get called only once. As we are implementing periodic timer, the _CommonCallback restarts the time for next callback and calls the user callback. Otherwise the library that we are trying to demonstrate here can not guarantee periodicity. It would be up to the user to re-register the callback and start the timer.

Leave a Reply

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

0
0
0
0
2
0