How do I get the most accurate realtime periodic interrupts in Linux?

I want to be interrupted at frequencies that are powers of ten, so enabling interrupts from /dev/rtc isn't ideal. I'd like to sleep 1 millisecond or 250 microseconds between interrupts.

Enabling periodic interrupts from /dev/hpet works pretty well, but it doesn't seem to work on some machines. Obviously I can't use it on machines that don't actually have a HPET. But I can't get it working on some machines that have hpet available as a clocksource either. For example, on a Core 2 Quad, the example program included in the kernel documentation fails at HPET_IE_ON when set to poll.

It would be nicer to use the itimer interface provided by Linux instead of interfacing with the hardware device driver directly. And on some systems, the itimer provides periodic interrupts that are more stable over time. That is, since the hpet can't interrupt at exactly the frequency I want, the interrupts start to drift from wall time. But I'm seeing some systems sleep way longer (10+ milliseconds) than they should using an itimer.

Here's a test program using itimer for interrupts. On some systems it will only print out one warning that's it slept about 100 microseconds or so over the target time. On others, it will print out batches of warning that it slept 10+ milliseconds over the target time. Compile with -lrt and run with sudo chrt -f 50 [name]

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <error.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/time.h>
#include <time.h>
#include <signal.h>
#include <fcntl.h>
#define NS_PER_SECOND 1000000000LL
#define TIMESPEC_TO_NS( aTime ) ( ( NS_PER_SECOND * ( ( long long int ) aTime.tv_sec ) ) \
    + aTime.tv_nsec )

int main()
{
    // Block alarm signal, will be waited on explicitly
    sigset_t lAlarm;
    sigemptyset( &lAlarm );
    sigaddset( &lAlarm, SIGALRM  );
    sigprocmask( SIG_BLOCK, &lAlarm, NULL );

    // Set up periodic interrupt timer
    struct itimerval lTimer;
    int lReceivedSignal = 0;

    lTimer.it_value.tv_sec = 0;
    lTimer.it_value.tv_usec = 250;
    lTimer.it_interval = lTimer.it_value;

    // Start timer
    if ( setitimer( ITIMER_REAL, &lTimer, NULL ) != 0 )
    {
        error( EXIT_FAILURE, errno, "Could not start interval timer" );
    }
    struct timespec lLastTime;
    struct timespec lCurrentTime;
    clock_gettime( CLOCK_REALTIME, &lLastTime );
    while ( 1 )
    {
        //Periodic wait
        if ( sigwait( &lAlarm, &lReceivedSignal ) != 0 )
        {
            error( EXIT_FAILURE, errno, "Failed to wait for next clock tick" );
        }
        clock_gettime( CLOCK_REALTIME, &lCurrentTime );
        long long int lDifference = 
            ( TIMESPEC_TO_NS( lCurrentTime ) - TIMESPEC_TO_NS( lLastTime ) );
        if ( lDifference  > 300000 )
        {
            fprintf( stderr, "Waited too long: %lld\n", lDifference  );
        }
        lLastTime = lCurrentTime;
    }
    return 0;
}
7
задан John Bartholomew 15 June 2011 в 21:03
поделиться