linuxtimerbeagleboard

How to implement highly accurate timers in Linux Userspace?


I have Angstrom Linux on my BeagleBoard.

I want to implement very accurate timer which can fire at every 500us. I read about hrtimers, but all the implementations I found were for kernel space. I want to implement it in user space.

Is there any API which which can call into these hrtimers, which I can use in userspace or any other way of implementing accurate timer in linux?

I have set the jiffy to a few nanoseconds.


Solution

  • Finally, after some more efforts I found a code which suggested to use timer_create(), clock_gettime() in conjunction with signal handling (handling SIGALRM), similar to what Basile Starynkevitch suggested in his comment.

    I tried it on my 1 GHz Beaglebone with clock type CLOCK_MONOTONIC for the interval of 500us.

    Out of 10000 times the timer expired, 2% of the time, it was exactly 500us (I ignored the nanosecond difference). and 96.6% of the time it was in the range of 500 +/- 10us. And the rest of the time, average error was not more than +/- 50us.

    This is the link for the code

    I have posted slightly modified version of code here. I did following modifications to the code:

    1. For small intervals ~10us the count was getting infinitely decremented, therefore I added the control on number of tests (count) inside the signal handler itself.

    2. Adding a printf in the middle of running timer costs a lot of time. Hence I stored the time difference in an array and then at the end i.e. after last test, I printed everything.

    3. I thought calculating time difference in unsigned long (i.e. in nanoseconds) is better than calculating in double (in seconds) as it is more accurate and may be fast. Hence I modified the timerdiff macro to output difference in nanoseconds. Since I am using interval of 500us or less the difference will never overflow the range of unsigned long.

    As you can see even after the modifications, only 2% of the results were accurate to <1us. Therefore now I am trying some more modifications like not running unnecessary linux processes, simplifying my program more, etc.

    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    #include <sys/types.h>
    #include <signal.h>
    #include <unistd.h>
    
    #define NSEC_PER_SEC 1000000000L
    
    #define MAX_TESTS    10000
    
    #define timerdiff(a,b) (((a)->tv_sec - (b)->tv_sec) * NSEC_PER_SEC + \
    (((a)->tv_nsec - (b)->tv_nsec)))
    
    static struct timespec prev = {.tv_sec=0,.tv_nsec=0};
    static int count = MAX_TESTS;
    unsigned long diff_times[MAX_TESTS];
    
    void handler( int signo )
    {
        struct timespec now;
        register int i, correct=0;
    
        if(count >= 0)
        {
            clock_gettime(CLOCK_MONOTONIC, &now);
            diff_times[count]=timerdiff(&now, &prev);
            prev = now;
            count --;
        }
    
        else
        {
            for(i=0; i<MAX_TESTS; ++i)
            {
                if(diff_times[i]/1000 < 510 && diff_times[i]/1000 > 490)
                {
                    printf("%d->\t", i);
                    correct++;
                }
                printf("%lu\n", diff_times[i]);
            }
            printf("-> %d\n", correct);
            exit(0);
        }
    }
    
    int main(int argc, char *argv[])
    {
        int i = 0;
        timer_t t_id;
    
        struct itimerspec tim_spec = {.it_interval= {.tv_sec=0,.tv_nsec=500000},
                        .it_value = {.tv_sec=1,.tv_nsec=0}};
    
        struct sigaction act;
        sigset_t set;
    
        sigemptyset( &set );
        sigaddset( &set, SIGALRM );
    
        act.sa_flags = 0;
        act.sa_mask = set;
        act.sa_handler = &handler;
    
        sigaction( SIGALRM, &act, NULL );
    
        if (timer_create(CLOCK_MONOTONIC, NULL, &t_id))
            perror("timer_create");
    
        clock_gettime(CLOCK_MONOTONIC, &prev);
    
        if (timer_settime(t_id, 0, &tim_spec, NULL))
            perror("timer_settime");
    
        while(1);
    
        return 0;
    }