Another hiccup with hardware timers

The hardware timer of the PC has another side effect when it comes to dealing with timers.

The "Oversleeping: errors in delays" section explains the behavior of the sleep-related functions. Timers are similarly affected by the design of the PC hardware. For example, let's consider the following C code:

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/neutrino.h>
#include <sys/netmgr.h>
#include <sys/syspage.h>

int main( int argc, char *argv[] )
{
    int pid;
    int chid;
    int pulse_id;
    timer_t timer_id;
    struct sigevent event;
    struct itimerspec timer;
    struct _clockperiod clkper;
    struct _pulse pulse;
    uint64_t last_cycles=-1;
    uint64_t current_cycles;
    float cpu_freq;
    time_t start;
    
    /* Get the CPU frequency in order to do precise time
       calculations. */
    cpu_freq =  SYSPAGE_ENTRY( qtime )->cycles_per_sec;
    
    /* Set our priority to the maximum, so we won't get disrupted
       by anything other than interrupts. */
    {
        struct sched_param param;
        int ret;

        param.sched_priority = sched_get_priority_max( SCHED_RR );
        ret = sched_setscheduler( 0, SCHED_RR, &param);
        assert ( ret != -1 );
    }
    
    /* Create a channel to receive timer events on. */
    chid = ChannelCreate( 0 );
    assert ( chid != -1 );
    
    /* Set up the timer and timer event. */
    event.sigev_notify            = SIGEV_PULSE;
    event.sigev_coid              = ConnectAttach ( ND_LOCAL_NODE,
                                                    0, chid, 0, 0 );
    event.sigev_priority          = getprio(0);
    event.sigev_code              = 1023;
    event.sigev_value.sival_ptr  = (void*)pulse_id;
    
    assert ( event.sigev_coid != -1 );
    
    if ( timer_create(  CLOCK_REALTIME, &event, &timer_id ) == -1 )
    {
        perror ( "can't create timer" );
        exit( EXIT_FAILURE );
    }
    
    /* Change the timer request to alter the behavior. */
    #if 1
          timer.it_value.tv_sec        = 0;
          timer.it_value.tv_nsec       = 1000000;
          timer.it_interval.tv_sec     = 0;
          timer.it_interval.tv_nsec    = 1000000;
    #else
          timer.it_value.tv_sec        = 0;
          timer.it_value.tv_nsec       = 999847;
          timer.it_interval.tv_sec     = 0;
          timer.it_interval.tv_nsec    = 999847;
    #endif
    
    /* Start the timer. */
    if ( timer_settime( timer_id, 0, &timer, NULL ) == -1 )
    {
        perror("Can't start timer.\n");
        exit( EXIT_FAILURE );
    }
    
    /* Set the tick to 1 ms. Otherwise if left to the default of
       10 ms, it would take 65 seconds to demonstrate. */
    clkper.nsec       = 1000000;
    clkper.fract      = 0;
    ClockPeriod ( CLOCK_REALTIME, &clkper, NULL, 0  );   // 1ms
    
    /* Keep track of time. */
    start = time(NULL);
    for( ;; )
    {
        /* Wait for a pulse. */
        pid = MsgReceivePulse ( chid, &pulse, sizeof( pulse ),
                                NULL );
        
        /* Should put pulse validation here... */
        current_cycles = ClockCycles();
        
        /* Don't print the first iteration. */
        if ( last_cycles != -1 )
        {
            float elapse = (current_cycles - last_cycles) /
                           cpu_freq;
            
            /* Print a line if the request is 1.05 ms longer than
               requested. */
            if ( elapse > .00105 )
            {
                 printf("A lapse of %f ms occurred at %d seconds\n",
                         elapse, time( NULL ) - start );
            }
        }
        
        last_cycles = current_cycles;
    }
}

The program checks to see if the time between two timer events is greater than 1.05 ms. Most people expect that given QNX Neutrino's great realtime behavior, such a condition will never occur, but it will, not because the kernel is misbehaving, but because of the limitation in the PC hardware. It's impossible for the OS to generate a timer event at exactly 1.0 ms; it will be .99847 ms. This has unexpected side effects.