How to Implement Periodic and Single Shot Timers in Linux?

In our program, we often need to do some tasks repeatedly with some time interval. We can think of a simple solution of having a loop with the actual task and a sleep()..  The sleep() function can wait for some time and then we can call a function to do the task. Problem here is that the sleep() function will block the current thread. We can not do anything else while waiting. This will not a acceptable in most practical scenarios. So the better solution would be – someone else (other thread) keeps track of the time and wakes you up when the timeout happens. This is an example of periodic timer.

Sometimes, our task might not be repetitive. We want to do something after some time. For example, in network programming, we send something to the peer and expect a response from there. But we don’t want to wait infinitely. If the response does not come within some specified time, we might want to do some cleanup. At the same time we don’t want to block while waiting for the timeout. Single shot times help in this situation.

Here we’ll implement our own C timer library for Linux. It will allow to create multiple timers – periodic or single shot. Full implementation is also available in github project – ctimer library.

The Timer Header File

The timer programming interface is defined in this header file.

#ifndef TIME_H
#define TIME_H
#include <stdlib.h>

typedef enum
{
TIMER_SINGLE_SHOT = 0, /*Periodic Timer*/
TIMER_PERIODIC         /*Single Shot Timer*/
} t_timer;

typedef void (*time_handler)(size_t timer_id, void * user_data);

int initialize();
size_t start_timer(unsigned int interval, time_handler handler, t_timer type, void * user_data);
void stop_timer(size_t timer_id);
void finalize();

#endif

Functions offered by this library:

  • initialize(): Creates the required data structures. It needs to be called only once in a program and before any other functions on this library.
  • start_timer(): Creates and start a timer. It returns the id of the newly created timer which could be used to stop the timer. This function has the following input parameters.
    • interval: Timeout interval in milli seconds. If interval is 10, the timer will expire (and call the callback handler) after 10 milli seconds. And in case of periodic timer, it will keep calling the handler in every 10 milli seconds.
    • handler: The callback function pointer. Library will call this function in every timer expiry.
    • type: Whether the timer is periodic or single shot. For periodic, the value is TIMER_PERIODIC and for single shot the value is TIMER_SINGLE_SHOT.
    • user_data: User can set any data as void pointer. The callback function will get this data. It can be used to share data between the timer creator and the expiry handler (the callback).
  • stop_timer(): Stops the timer specified by the input timer id. Timer id is returned by the start_timer() function in time of creating the timer.
  • finalize(): It should be called when the timer library will no longer be required. It stops all running timers and deletes all associated data structures.

Example Program

Let’s first see how to use this library before going into the actual library implementation.

In this example, We’ll create three timers – one single shot and two periodic. The single shot timer will expire after 20 milli seconds. One periodic timer will expire in every 100 milli seconds and the other in every 2000 milli seconds (2 seconds).

#include <stdio.h>
#include <stdlib.h>

#include "ctimer.h"

void time_handler1(size_t timer_id, void * user_data)
{
    printf("Single shot timer expired.(%d)\n", timer_id);
}

void time_handler2(size_t timer_id, void * user_data)
{
    printf("100 ms timer expired. (%d)\n", timer_id);
}

void time_handler3(size_t timer_id, void * user_data)
{
    printf("2000 ms timer expired. (%d)\n", timer_id);
}

void main()
{
    size_t timer1, timer2, timer3;

    initialize();

    timer1 = start_timer(200, time_handler1, TIMER_SINGLE_SHOT, NULL);
    timer2 = start_timer(100, time_handler2, TIMER_PERIODIC, NULL);
    timer3 = start_timer(2000, time_handler3, TIMER_PERIODIC, NULL);

    sleep(6);

    stop_timer(timer1);
    stop_timer(timer2);
    stop_timer(timer3);

    finalize();
}

We called the initialize() function to initialize the module. In next three lines we started three timers by calling start_timer() function. We passed the interval, the call handler function, timer type as input. The handler functions will get called on expiry of the respective timers.

The first timer is a single shot one that will expire after 200 milli seconds. The next two timers are periodic ones that will expire in every 100 milli second and 2000 (2 second) milli seconds respectively.

Then we waited 6 seconds before stopping all three timers. Finally we called the finalize() function.

100 ms timer expired. (9536128)
100 ms timer expired. (9536128)
Single shot timer expired.(9536080)
100 ms timer expired. (9536128)
100 ms timer expired. (9536128)
100 ms timer expired. (9536128)
100 ms timer expired. (9536128)
100 ms timer expired. (9536128)
100 ms timer expired. (9536128)
100 ms timer expired. (9536128)
100 ms timer expired. (9536128)
100 ms timer expired. (9536128)
100 ms timer expired. (9536128)
100 ms timer expired. (9536128)
100 ms timer expired. (9536128)
100 ms timer expired. (9536128)
100 ms timer expired. (9536128)
100 ms timer expired. (9536128)
100 ms timer expired. (9536128)
100 ms timer expired. (9536128)
2000 ms timer expired. (9536176)
100 ms timer expired. (9536128)
100 ms timer expired. (9536128)
...
...

Timer Library Implementation

Here we’ll see the implementation of the timer library. It internally uses the timerfd system calls.

/*ctimer.c*/

#include <stdint.h>
#include <string.h>
#include <sys/timerfd.h>
#include <pthread.h>
#include <poll.h>
#include <stdio.h>

#include "mytimer.h"

#define MAX_TIMER_COUNT 1000

struct timer_node
{
    int                 fd;
    time_handler        callback;
    void *              user_data;
    unsigned int        interval;
    t_timer             type;
    struct timer_node * next;
};

static void * _timer_thread(void * data);
static pthread_t g_thread_id;
static struct timer_node *g_head = NULL;

int initialize()
{
    if(pthread_create(&g_thread_id, NULL, _timer_thread, NULL))
    {
        /*Thread creation failed*/
        return 0;
    }

    return 1;
}

size_t start_timer(unsigned int interval, time_handler handler, t_timer type, void * user_data)
{
    struct timer_node * new_node = NULL;
    struct itimerspec new_value;

    new_node = (struct timer_node *)malloc(sizeof(struct timer_node));

    if(new_node == NULL) return 0;

    new_node->callback  = handler;
    new_node->user_data = user_data;
    new_node->interval  = interval;
    new_node->type      = type;

    new_node->fd = timerfd_create(CLOCK_REALTIME, 0);

    if (new_node->fd == -1)
    {
        free(new_node);
        return 0;
    }

    new_value.it_value.tv_sec = interval / 1000;
    new_value.it_value.tv_nsec = (interval % 1000)* 1000000;

    if (type == TIMER_PERIODIC)
    {
      new_value.it_interval.tv_sec= interval / 1000;
      new_value.it_interval.tv_nsec = (interval %1000) * 1000000;
    }
    else
    {
      new_value.it_interval.tv_sec= 0;
      new_value.it_interval.tv_nsec = 0;
    }

    timerfd_settime(new_node->fd, 0, &new_value, NULL);

    /*Inserting the timer node into the list*/
    new_node->next = g_head;
    g_head = new_node;

    return (size_t)new_node;
}

void stop_timer(size_t timer_id)
{
    struct timer_node * tmp = NULL;
    struct timer_node * node = (struct timer_node *)timer_id;

    if (node == NULL) return;

    if(node == g_head)
    {
        g_head = g_head->next;
    } else {

      tmp = g_head;

      while(tmp && tmp->next != node) tmp = tmp->next;

      if(tmp)
      {
          /*tmp->next can not be NULL here*/
          tmp->next = tmp->next->next;
          close(node->fd);
          free(node);
      }
    }
}

void finalize()
{
    while(g_head) stop_timer((size_t)g_head);

    pthread_cancel(g_thread_id);
    pthread_join(g_thread_id, NULL);
}

struct timer_node * _get_timer_from_fd(int fd)
{
    struct timer_node * tmp = g_head;

    while(tmp)
    {
        if(tmp->fd == fd) return tmp;

        tmp = tmp->next;
    }
    return NULL;
}

void * _timer_thread(void * data)
{
    struct pollfd ufds[MAX_TIMER_COUNT] = {{0}};
    int iMaxCount = 0;
    struct timer_node * tmp = NULL;
    int read_fds = 0, i, s;
    uint64_t exp;

    while(1)
    {
        pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
        pthread_testcancel();
        pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);

        iMaxCount = 0;
        tmp = g_head;

        memset(ufds, 0, sizeof(struct pollfd)*MAX_TIMER_COUNT);
        while(tmp)
        {
            ufds[iMaxCount].fd = tmp->fd;
            ufds[iMaxCount].events = POLLIN;
            iMaxCount++;

            tmp = tmp->next;
        }
        read_fds = poll(ufds, iMaxCount, 100);

        if (read_fds <= 0) continue;

        for (i = 0; i < iMaxCount; i++)
        {
            if (ufds[i].revents & POLLIN)
            {
                s = read(ufds[i].fd, &exp, sizeof(uint64_t));

                if (s != sizeof(uint64_t)) continue;

                tmp = _get_timer_from_fd(ufds[i].fd);

                if(tmp && tmp->callback) tmp->callback((size_t)tmp, tmp->user_data);
            }
        }
    }

    return NULL;
}

Let’s briefly discuss some important timerfd system calls that we used in this library.

  • The timerfd_create() creates a timer and returns a handler of the timer, fd (file descriptor). This fd can be used to start or monitor the timer later.
  • The timerfd_settime() starts the timer. We can also specify whether the timer is periodic or single shot.

If a timer expires, the corresponding file descriptors (fd) will be readable using read() system call. We have one thread in our library that continuously checks all file descriptors (fd) using poll() system call. If any file descriptor is set (POLLIN), we call the expiry callback function of the corresponding timer.

The timer_node data structure stores all information related to a particular timer. We have a linked list (g_head) to store contexts of multiple timers. We implemented the following functions.

  • initialize(): It creates a thread that monitors all the timers if any one is expired.
  • start_timer(): It first allocates memory (new_node) for the timer and stores related information such as callback function, interval, type (periodic or single shot) in the new_node structure. Then it creates the timer using timerfd_create() function and stores the returned fd (file descriptor) in new_node. Time out interval is set in new_value.it_value.tv_sec variable and in case of periodic timer the interval is set in new_value.it_interval.tv_sec variable. Then it starts the timer using timerfd_settime() function. At the end it inserts the timer structure (new_node) into the linked list.
  • stop_timer(): It stops the timer by calling close() system call with the file descriptor (fd) of the timer. It also removed the timer data structure from the linked list.
  • finalize(): It first stops all running timers and then stops the timer thread.
  • _timer_thread(): It continuously checks if any timer file descriptor (fd) is set using poll() system call. If any timer file descriptor is set and readable using read() system call, then it calls the callback function of the timer with timer id and user_data.

Github reference: https://github.com/srikanta00/ctimer

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.

36 thoughts on “How to Implement Periodic and Single Shot Timers in Linux?”

  1. Hi,

    Firstly, your post is very well explained. Thank you. I have few doubts. I’m trying to create a multi threaded timer which could be either single shot or periodic. I tried to use timer_create and timer_settime and timer_gettime from the time.h module. “We to have one thread in our library that continuously checks all file descriptors (fd) using poll() system call.” Instead of having one thread which checks continuously checks the expiry of a timer, I want to have individual timer threads which will asynchronously work and call the callback on expiry. Is there any particular reason to use fd module instead of the one I mentioned above? Please clarify my doubts. Thanks in advance!

    1. This is basically a design choice. You have to justify your choice based on the problem you want to solve. The main benefit of having single timer thread instead of many is that it will require less resource (CPU and memory). But you need to be careful, you should not perform heavy duty work in the callback function. If you have to do so, then better to post events that will be executed in other thread and return from the callback function as soon as possible.
      If you have per timer thread then you can have more flexibility in the callback function. Even if you do some work that might take significant time, it will not impact the other timers. But in most situations, it’s better not to do time consuming task in callback. So having per timer thread will not add much value. Then again, it will depend on your situation.

  2. It would be better to add protection in stop_timer(), some code might call stop_timer() more than one time:

    {
    struct timer_node * tmp = NULL;
    struct timer_node * node = (struct timer_node *)timer_id;
    int timer_active = FALSE;

    if (node == NULL) return;

    if(node == g_head)
    {
    g_head = g_head->next;
    timer_active = TRUE;
    } else {

    tmp = g_head;

    while(tmp && tmp->next != node) tmp = tmp->next;

    if(tmp)
    {
    /*tmp->next can not be NULL here*/
    tmp->next = tmp->next->next;
    timer_active = TRUE;
    }
    }

    if(timer_active)
    {
    close(node->fd);
    if(node) free(node);
    }
    }

    1. You are right. We should delete the node only if it’s present in the timer list. Otherwise, it will cause segfault (memory violation) if stop_time() is called multiple times for a particular timer.
      Thanks Breadlgh for pointing that out. I’ll make necessary changes in the code.

        1. I think discussing here is better because it will help others also. You can put down your doubts here. I’ll try my best to explain.

  3. The timer doesn’t work for period of more than 999ms. I tried your example code and there seems to be a problem with large values of time period. Did you try timer period of more than 999ms in the current state of the program?

    1. You are right. Thanks for pointing that out.
      This section of code:

      new_value.it_value.tv_sec = 0;
      new_value.it_value.tv_nsec = interval * 1000000;

      if (type == TIMER_PERIODIC)
      {
      new_value.it_interval.tv_nsec = interval * 1000000;
      }
      else
      {
      new_value.it_interval.tv_nsec = 0;
      }

      new_value.it_interval.tv_sec= 0;

      would be:

      new_value.it_value.tv_sec = interval / 1000;
      new_value.it_value.tv_nsec = (interval % 1000)* 1000000;

      if (type == TIMER_PERIODIC)
      {
      new_value.it_interval.tv_sec= interval / 1000;
      new_value.it_interval.tv_nsec = (interval %1000) * 1000000;
      }
      else
      {
      new_value.it_interval.tv_sec= 0;
      new_value.it_interval.tv_nsec = 0;
      }

      I did not test the code yet. I’ll update the article after testing.

    1. Seems like you are not compiling the mytimer.c file and linking with the object file. I made the whole program downloadable with a Makefile. Link is at the bottom of the article. Down load and run the following commands.

      tar -zxvf timer_module.tar.gz
      cd timer_module/
      make
      ./test

  4. I need to embed your code into my c++ code but it does not compile fine, The following error arises :>
    **********************************************************************
    error: invalid conversion from ‘void (*)()’ to ‘time_handler {aka void (*)(unsigned int, void*)}’ [-fpermissive]
    time_handler timer_handler1=adc_timer_handler;
    *********************************************************************

    note: adc_timer_handler is my callback function.
    do you suggest a solution?

    1. Same for me as well.
      As this uses static functions inside not possible to use for object oriented programs

      1. Sorry, I missed to address Mohamed’s issue last year. If you are having same error, then the problem is not due the static functions used inside the library. It’s because of the signature mismatch between the callback functions. This library expects the signature of the callback to be ‘void (*)(unsigned int, void*)’.
        typedef void (*time_handler)(size_t timer_id, void * user_data);
        But the signature provided is ‘void (*)()’. So, you need to change the signature of your callback function.
        I don’t see any reason why this library can not be used in C++ code even though it is written in C (non object oriented way). If you face any C-C++ interoperability problem, let me know.

  5. A nice example for timer implementation. I have a few comments which can help you to improve the performance of your program.

    1. read_fds = poll(ufds, iMaxCount, 100); This will introduce a bit of load on the system, as it will update the FDs after every 100 m.secs even if no timer has been started or stopped.
    An optimization could be wait here indefinitely and signal the thread whenever a timer is started/stopped.

    2. In function stop_timer()
    tmp = g_head;
    while(tmp && tmp->next != node) tmp = tmp->next;
    if(tmp && tmp->next)
    {
    tmp->next = tmp->next->next;
    }
    —The above part is a bit pointless when node == g_head as you already found the item. Suggestion to put it inside an else statement.
    and the test for tmp->next in “if(tmp && tmp->next)” is not really needed as it can’t be that tmp==NULL and tmp->next==NULL also.

    1. Thanks for the in-depth analysis of the code.
      1. We can wait indefinitely in poll() function until any timer is expired but it has two problems.
      a) While poll() is waiting of the existing timers which will expire, say, in far future, new timer is added to the timer module with shorter duration. That timer will not be added to the FD list and will not get chance to expire.
      b) You can not stop the program gracefully until any timer expires. For example, your next timer will expire in next one hour. So in next one hour you won’t be able to terminate the program gracefully.

      2. You are right, node == g_head and next few lines should be in else section. I did that. You are also right that checking tmp->next for NULL is meaningless. If tmp is not null tmp->next is definitely your node. So tmp->next can not be NULL.

      1. Hi Srikanta,

        As I have mentioned a signaling mechanism can solve both the problems you have mentioned in point a and b.
        So if you will use a signaling mechanism in start_timer and stop_timer functions which will signal the main thread whenever a new timer should be added or an existing to be removed. In the main thread on receipt of the signal it will come out from poll and it will find out that no fd is set thus continue to outer loop and will update the ufds.

        Thanks.

  6. Srikanta, very good explanation to start off with timer concept. After going through your code, couple of questions popped up in my mind with respect to performance of whole timer implementation. It would be really great if you/some one can answer my following questions.

    1. is this solution scalable in case of huge number of active timers let us assume 1 Million or more?
    2. _get_timer_from_fd(): This function doing a linear search on linked list to retrieve a node which is O(n) complexity, which may cause delay in executing callback function. suppose if 1000 timers expired at same time, there might be delay in executing 1000th call back function due to 999 times linear search.
    3.I couldn’t understand why read() function call was used, what would be the 64-bytes data written into exp variable and we are not using exp anywhere, is read() call required here?
    4. can you explain if (s != sizeof(uint64_t)) continue;
    5. for (i = 0; i < iMaxCount; i++)
    {
    I'm really against to this for{} loop becasue everytime poll() returns it itereate iMaxCount times in worst case. This is going to be a big blow to software. is there any other mechanism to optimize this?

    1. This program is for illustration purpose. I wanted to keep the program as simple as possible. It is not suitable for high performance production system. It will not also scale up to million timers. For our production software we use B-tree and jiffies for timer tracking. It would be a complicated topic and complex program. I’ll try to cover that sometime.
      Your other performance related concerns are also valid but I don’t want to change those at this moment to maintain its simplicity.

      3. Even though we are not using exp, we need to call read(). Otherwise the associated FD will return from next poll() even if the timer does not expire and give you wrong expiry. You can use expiry count (exp) if you want though.
      4. if (s != sizeof(uint64_t)) continue; If read does not read 64 bits (8 bytes), then some error happened. In this situation we are skipping that timer for that iteration.

    1. In my example I assumed the interval to be in seconds for illustration purpose. But we can change the code little bit to get millisecond level granularity.

      If your interval is in milliseconds, these lines of start_timer() function should be changed:

      new_value.it_value.tv_sec = interval;
      new_value.it_value.tv_nsec = 0;
      if (type == TIMER_PERIODIC)
      {
      new_value.it_interval.tv_sec = interval;
      }
      else
      {
      new_value.it_interval.tv_sec = 0;
      }
      new_value.it_interval.tv_nsec = 0;

      Replace the above lines with the new ones.

      new_value.it_value.tv_sec = 0;
      new_value.it_value.tv_nsec = interval * 1000000;

      if (type == TIMER_PERIODIC)
      {
      new_value.it_interval.tv_nsec = interval * 1000000;
      }
      else
      {
      new_value.it_interval.tv_nsec = 0;
      }

      new_value.it_interval.tv_sec= 0;

      1. very good example , i tried to implement resolution in mil seconds unfortunately your suggestion does not work any suggestion?

  7. I created the header file and tried to run the example program given above and encountered the following errors and warnings on MAC OSX:

    mohanishs-air:Desktop mohanishmankar$ gcc timer.c
    timer.c: In function ‘main’:
    timer.c:31:5: warning: implicit declaration of function ‘sleep’ [-Wimplicit-function-declaration]
    sleep(60);
    ^~~~~
    Undefined symbols for architecture x86_64:
    “_finalize”, referenced from:
    _main in ccRtE3Is.o
    “_initialize”, referenced from:
    _main in ccRtE3Is.o
    “_start_timer”, referenced from:
    _main in ccRtE3Is.o
    “_stop_timer”, referenced from:
    _main in ccRtE3Is.o
    ld: symbol(s) not found for architecture x86_64
    collect2: error: ld returned 1 exit status

    Please help me out. Thank you.

    1. The implementation of this article is based on timerfd which is Linux specific. I don’t think this is going to work on OSX. But there must be something equivalent in OSX. I don’t have MAC system to try with. If I can get that I’ll update you soon.

  8. Is time_handler can be called on a different thread other than the thread that initialized the timer (start_timer)?

    1. In fact the timer_handler will be called from different thread context than the thread that started the timer using start_timer() function.
      In the example program above, the main thread created the timers but the timer_handler functions got called from different thread. It is not obvious from the output but you can modify the program to print the current thread ids from the main() function and from the event handlers. You will see that the main thread id (that started the timers) is different from the thread ids of the event_handlers.
      From the implementation we can see that: the initialize() function is creating a thread by this line “if(pthread_create(&g_thread_id, NULL, _timer_thread, NULL))”. And in the thread function _timer_thread() is calling the handler callbacks from this line “if(tmp && tmp->callback) tmp->callback((size_t)tmp, tmp->user_data);”. That means all the time_handlers will be called from the thread context of _timer_thread().
      Please remember that this is my implementation. You can implement any threading model using timerfd system calls.

  9. Very nice.
    Couple of minor comments:
    a) Typo:
    if (read_fds <= 0) continue;

    if (read_fds <= 0) continue;

    I'm guessing you inadvertently pasted twice as this seems redundant
    b)
    Each time through while(1) loop, you:
    memset(ufds, 0, sizeof(struct pollfd)*MAX_TIMER_COUNT);
    which sets all 1000 ufds to 0. Doesn't this waste time? Could you possibly replace it with:
    while(tmp)
    {
    memset(&ufds[iMaxCount], 0, sizeof(struct pollfd));
    at the beginning of the while(tmp) loop? Indeed, I'm not sure it's even needed as at the beginning of the thread you set ufds to 0 using
    struct pollfd ufds[MAX_TIMER_COUNT] = {{0}};
    and you only write into ufds being used and you write all fields except revents which isn't being used; and if you wanted to you could just add a line to set it to 0 if you really wanted to be safe.
    Summary: very minor comments as the example is very nicely done.

    1. Thank you for your comment.
      You are right, the line “if (read_fds <= 0) continue;" pasted twice.
      Your second point is also correct. We don't need to unnecessarily set all fds to 0 if you are concerned about performance. The initialization of the array "struct pollfd ufds[MAX_TIMER_COUNT] = {{0}};" will not help much. This is initialized once but the loop will iterate many times and the number of fds can increase and decrease several times. Putting the line "memset(&ufds[iMaxCount], 0, sizeof(struct pollfd));" at the beginning of the while(tmp) should be enough, as you mentioned, to be in the safe side. I'll update this article accordingly.
      Thanks again for helping us to make our article better.

Leave a Reply

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

14
7
11
5
21
5